Setup

library(data.table)
library(DBI)
library(ggplot2)
library(cowplot)
library(grid)
library(lme4)
library(lmerTest)

Sys.setlocale("LC_TIME", "en_US.UTF-8") # Print English date format
[1] "en_US.UTF-8"
en_US.UTF-8
# Sys.setlocale("LC_TIME", "nl_NL.UTF-8") # Print Dutch date format

number_format <- scales::number_format(big.mark = ",", decimal.mark = ".") # Print English number format
# number_format <- scales::number_format(big.mark = ".", decimal.mark = ",") # Print Dutch number format

theme_paper <- theme_classic(base_size = 12) + 
  theme(axis.text = element_text(colour = "black"),
        panel.grid.major.y = element_line(colour = "grey92"))

School closure and opening dates

Sources:

date_schools_closed <- as.POSIXct("2020-03-16")
date_schools_opened <- as.POSIXct("2020-06-02")

Handle database connections

db_connect <- function() {
  db <- dbConnect(RSQLite::SQLite(), file.path("..", "data", "noordhoff.sqlite"))
  return(db)
}

db_disconnect <- function(db) {
  dbDisconnect(db)
}

Data

The database contains all SlimStampen data collected via Noordhoff’s platform in three courses: Stepping Stones (English), Grandes Lignes (French), and Neue Kontakte (German).

Trial-level response data are stored in the responses table. Book information, such as the course year, book title, and chapter, are stored in the book_info table.

responses

Column Type Explanation
date int UNIX time stamp [s]
user_id chr unique user identifier
method chr course
start_time int elapsed time since session start [ms]
rt int response time [ms]
duration int trial duration [ms]
fact_id int unique fact identifier (within chapter)
correct int response accuracy
answer chr user’s response
choices int number of answer choices (1 == open response)
backspace_used dbl user pressed backspace during trial
backspace_used_first dbl user erased first character of response
study int trial was a study trial
answer_language chr language of the answer
subsession int identifies part within learning session
book_info_id chr unique identifier of book information

book_info

Column Type Explanation
book_info_id chr unique identifier of book information
method_group chr year and edition
book_title chr book title (incl. year, level, edition)
book_type chr type of book
chapter chr chapter number and title

Preview first 10 rows

db <- db_connect()
responses_top <- dbGetQuery(db, "SELECT * FROM responses_noduplicates LIMIT 10")
responses_top
book_info_top <- dbGetQuery(db, "SELECT * FROM book_info LIMIT 10")
book_info_top
db_disconnect(db)

Performance

There are several measures of learning performance we can look at. The most straight-forward of these are response accuracy and response time.

Important factors to keep in mind: question type (multiple choice or open answer) and language. Note that we cannot distinguish between NL-X and X-X, since we only know the language of the answer.

Response accuracy

Whole population

db <- db_connect()
correct <- dbGetQuery(db, 
                      "SELECT r.method AS 'method',
                      DATE(r.date + 3600, 'unixepoch') AS 'doy',
                      r.user_id AS 'user',
                      r.choices > 1 AS 'mcq',
                      r.correct AS 'correct',
                      COUNT(*) AS 'n'
                      FROM 'responses_noduplicates' r
                      WHERE r.study == 0
                      GROUP BY r.method,
                      DATE(r.date + 3600, 'unixepoch'),
                      r.user_id,
                      r.choices > 1,
                      r.correct"
)
setDT(correct)
db_disconnect(db)

Fill in missing rows (where all trials on a day were correct/incorrect):

correct <- tidyr::complete(correct, tidyr::nesting(method, doy, user, mcq), correct, fill = list(n = 0))
setDT(correct)
correct[, mcq := as.logical(mcq)]
accuracy <- correct[, .(accuracy = n[correct == 1]/sum(n), n = sum(n)), by = .(method, doy, user, mcq)]

Add a school year column (cutoff date: 1 August):

accuracy[, doy_posix := as.POSIXct(doy)]
accuracy[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Add sensible course names:

accuracy[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Align school years:

accuracy[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
accuracy[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

accuracy[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
accuracy[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
accuracy_by_week_and_user <- accuracy[, .(accuracy = sum(accuracy*n)/sum(n)), by = .(course, school_year, doy_posix_aligned_week, user, mcq)]
accuracy_by_week <- accuracy_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
                              accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, mcq)]

Add question type labels:

accuracy_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]

Plot response accuracy by week (mean +/- 1 SE).

p_acc <- ggplot(accuracy_by_week[(course == "English" & mcq == TRUE) | course == "French",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(. ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(limits = c(.7, 1), labels = scales::percent_format(accuracy = 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper
p_acc
Warning: Removed 24 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type.pdf", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type.eps", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/acc_by_question_type.png", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).

By level and year

db <- db_connect()
correct_strat <- dbGetQuery(db, 
                      "SELECT r.method AS 'method',
                      r.book_info_id AS 'book_info_id',
                      DATE(r.date + 3600, 'unixepoch') AS 'doy',
                      r.user_id AS 'user',
                      r.choices > 1 AS 'mcq',
                      r.correct AS 'correct',
                      COUNT(*) AS 'n'
                      FROM 'responses_noduplicates' r
                      WHERE r.study == 0
                      GROUP BY r.method,
                      r.book_info_id,
                      DATE(r.date + 3600, 'unixepoch'),
                      r.user_id,
                      r.choices > 1,
                      r.correct"
)
setDT(correct_strat)
db_disconnect(db)

Fill in missing rows (where all trials on a day were correct/incorrect):

correct_strat <- tidyr::complete(correct_strat, tidyr::nesting(method, book_info_id, doy, user, mcq), correct, fill = list(n = 0))
setDT(correct_strat)
correct_strat[, mcq := as.logical(mcq)]

Add book information:

db <- db_connect()
book_info <- dbGetQuery(db, "SELECT DISTINCT * FROM 'book_info'")
db_disconnect(db)

setDT(book_info)
correct_strat[book_info[book_type == "Hoofdboek",], on  = "book_info_id", c("method_group", "book_title") := .(i.method_group, i.book_title)]

Add sensible course names:

correct_strat[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Add a school year column (cutoff date: 1 August):

correct_strat[, doy_posix := as.POSIXct(doy)]
correct_strat[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Simplify level names:

# Keep all distinctions
correct_strat[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
correct_strat[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
correct_strat[, level := dplyr::case_when(
  grepl( "hv", book_title) ~ "General secondary\n(havo)",
  grepl("vmbo", book_title) ~ "Pre-vocational\n(vmbo)",
  grepl("havo", book_title) ~ "General secondary\n(havo)",
  grepl("vwo", book_title) ~ "Pre-university\n(vwo)",
  TRUE ~ "Other")]
correct_strat[, level := factor(level, levels = c("Other", "Pre-vocational\n(vmbo)", "General secondary\n(havo)", "Pre-university\n(vwo)"))]

Simplify year names:

correct_strat[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]

Consolidate by day:

accuracy_strat <- correct_strat[, .(accuracy = n[correct == 1]/sum(n), n = sum(n)), by = .(school_year, doy_posix, course, level, year, user, mcq)]

Align school years:

accuracy_strat[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
accuracy_strat[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week. Each day is assigned the date of the most recent Monday.

accuracy_strat[, doy_posix_week := cut.POSIXt(doy_posix, "week")]
accuracy_strat[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
accuracy_strat_by_week_and_user <- accuracy_strat[, .(accuracy = sum(accuracy*n)/sum(n)), by = .(course, school_year, doy_posix_aligned_week, level, year, user, mcq)]
accuracy_strat_by_week <- accuracy_strat_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
                              accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, level, year, mcq)]

Add question type labels:

accuracy_strat_by_week_and_user[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
accuracy_strat_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]

How many unique users per group?

accuracy_strat_by_week_and_user[, .(unique_users = length(unique(user))),  by = .(course, level, year, school_year, question_type)]

Plot response accuracy by week (mean +/- 1 SE).

p_acc_level_year <- ggplot(accuracy_strat_by_week[course == "French" & level != "Other",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(.4, 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_acc_level_year
Warning: Removed 11 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type_french_level_year.pdf", width = 9, height = 5)
Warning: Removed 11 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type_french_level_year.eps", width = 9, height = 5)
Warning: Removed 11 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/acc_by_question_type_french_level_year.png", width = 9, height = 5)
Warning: Removed 11 row(s) containing missing values (geom_path).
p_acc_level_year <- ggplot(accuracy_strat_by_week[course == "English" & level != "Other" & question_type == "Multiple\nchoice",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line() +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(.4, 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_acc_level_year
Warning: Removed 5 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type_english_level_year.pdf", width = 9, height = 5)
Warning: Removed 5 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type_english_level_year.eps", width = 9, height = 5)
Warning: Removed 5 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/acc_by_question_type_english_level_year.png", width = 9, height = 5)
Warning: Removed 5 row(s) containing missing values (geom_path).

By level

accuracy_level_by_week_and_user <- accuracy_strat[, .(accuracy = sum(accuracy*n)/sum(n)), by = .(course, school_year, doy_posix_aligned_week, level, user, mcq)]

accuracy_level_by_week <- accuracy_level_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
                              accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, level, mcq)]

Add question type labels:

accuracy_level_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]

How many users in each group?

accuracy_level_by_week_and_user[, .(unique_users = length(unique(user))),  by = .(course, level, school_year, mcq)]

Plot response accuracy by week (mean +/- 1 SE).

p_acc_level <- ggplot(accuracy_level_by_week[((course == "English" & question_type == "Multiple\nchoice") | course == "French") & level != "Other",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(.6, 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_acc_level
Warning: Removed 20 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type_level.pdf", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type_level.eps", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/acc_by_question_type_level.png", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).

By year

accuracy_year_by_week_and_user <- accuracy_strat[, .(accuracy = sum(accuracy*n)/sum(n)), by = .(course, school_year, doy_posix_aligned_week, year, user, mcq)]

accuracy_year_by_week <- accuracy_year_by_week_and_user[, .(accuracy_mean = mean(accuracy, na.rm = TRUE),
                              accuracy_se = sd(accuracy, na.rm = TRUE)/sqrt(.N), n = .N), by = .(course, school_year, doy_posix_aligned_week, year, mcq)]

Add question type labels:

accuracy_year_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]

How many users in each group?

accuracy_year_by_week_and_user[, .(unique_users = length(unique(user))),  by = .(course, year, school_year, mcq)]

Plot response accuracy by week (mean +/- 1 SE).

p_acc_year <- ggplot(accuracy_year_by_week[((course == "English" & question_type == "Multiple\nchoice") | course == "French") & year != "Other",],
            aes(x = as.POSIXct(doy_posix_aligned_week), y = accuracy_mean, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(year ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1.05, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = accuracy_mean - accuracy_se, ymax = accuracy_mean + accuracy_se, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
  coord_cartesian(ylim = c(.6, 1)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Accuracy",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_acc_year
Warning: Removed 14 row(s) containing missing values (geom_path).

ggsave("../output/acc_by_question_type_year.pdf", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).
ggsave("../output/acc_by_question_type_year.eps", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/acc_by_question_type_year.png", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).

Regression model

Fit a mixed effects model to the daily accuracy data:

accuracy[, period := dplyr::case_when(
  doy_posix_aligned >= date_schools_opened ~ "post-lockdown",
  doy_posix_aligned >= date_schools_closed & doy_posix_aligned < date_schools_opened ~ "during-lockdown",
  doy_posix_aligned < date_schools_opened ~ "pre-lockdown"
)]
# Reorder factor levels so that intercept is pre-lockdown open answer in 19/20
accuracy[, period := factor(period, levels = c("pre-lockdown", "during-lockdown", "post-lockdown"))]
accuracy[, school_year := factor(school_year, levels = c("19/20", "18/19"))]
accuracy[, mcq := factor(mcq, levels = c(FALSE, TRUE))]

Since we know the number of trials per day and the proportion correct (accuracy), we can use a binomial GLMM:

if(!file.exists("../output/m_acc_fit.rds")) {
  m_acc <- glmer(accuracy ~ period*school_year*mcq + (1 | user) + (1 | course),
                 data = accuracy[(course == "English" & mcq == TRUE) | course == "French",],
                 family = "binomial", 
                 weights = n,
                 nAGQ = 0,
                 control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 1e6)))
  saveRDS(m_acc, "../output/m_acc_fit.rds")
} else {
  m_acc <- readRDS("../output/m_acc_fit.rds")
}

m_acc_summary <- summary(m_acc)
m_acc_summary
Generalized linear mixed model fit by maximum likelihood (Adaptive
  Gauss-Hermite Quadrature, nAGQ = 0) [glmerMod]
 Family: binomial  ( logit )
Formula: accuracy ~ period * school_year * mcq + (1 | user) + (1 | course)
   Data: 
accuracy[(course == "English" & mcq == TRUE) | course == "French",      ]
Weights: n
Control: 
glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 1e+06))

     AIC      BIC   logLik deviance df.resid 
 4934770  4934930 -2467371  4934742   668527 

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-22.1127  -0.9205   0.1614   1.0939  11.4403 

Random effects:
 Groups Name        Variance Std.Dev.
 user   (Intercept) 0.34971  0.5914  
 course (Intercept) 0.05504  0.2346  
Number of obs: 668541, groups:  user, 133419; course, 2

Fixed effects:
                                                Estimate Std. Error
(Intercept)                                     1.631889   0.165913
periodduring-lockdown                           0.264728   0.002760
periodpost-lockdown                             0.159475   0.004190
school_year18/19                                0.116285   0.002580
mcqTRUE                                         0.959425   0.002109
periodduring-lockdown:school_year18/19         -0.316933   0.004432
periodpost-lockdown:school_year18/19           -0.251656   0.006437
periodduring-lockdown:mcqTRUE                  -0.333631   0.002913
periodpost-lockdown:mcqTRUE                    -0.286879   0.004562
school_year18/19:mcqTRUE                        0.004370   0.002681
periodduring-lockdown:school_year18/19:mcqTRUE  0.384829   0.004778
periodpost-lockdown:school_year18/19:mcqTRUE    0.344909   0.007078
                                                z value Pr(>|z|)    
(Intercept)                                       9.836   <2e-16 ***
periodduring-lockdown                            95.917   <2e-16 ***
periodpost-lockdown                              38.065   <2e-16 ***
school_year18/19                                 45.073   <2e-16 ***
mcqTRUE                                         454.977   <2e-16 ***
periodduring-lockdown:school_year18/19          -71.502   <2e-16 ***
periodpost-lockdown:school_year18/19            -39.095   <2e-16 ***
periodduring-lockdown:mcqTRUE                  -114.525   <2e-16 ***
periodpost-lockdown:mcqTRUE                     -62.879   <2e-16 ***
school_year18/19:mcqTRUE                          1.630    0.103    
periodduring-lockdown:school_year18/19:mcqTRUE   80.540   <2e-16 ***
periodpost-lockdown:school_year18/19:mcqTRUE     48.730   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
                  (Intr) prddr- prdps- sc_18/19 mcTRUE prdd-:_18/19
prddrng-lck       -0.008                                           
prdpst-lckd       -0.005  0.336                                    
schl_y18/19       -0.009  0.453  0.290                             
mcqTRUE           -0.010  0.534  0.344  0.596                      
prdd-:_18/19       0.005 -0.631 -0.212 -0.506   -0.343             
prdp-:_18/19       0.003 -0.228 -0.655 -0.347   -0.233  0.239      
prddr-:TRUE        0.007 -0.825 -0.271 -0.414   -0.649  0.520      
prdps-:TRUE        0.004 -0.263 -0.827 -0.259   -0.408  0.166      
s_18/19:TRU        0.007 -0.411 -0.264 -0.838   -0.701  0.451      
prdd-:_18/19:TRUE -0.004  0.510  0.167  0.440    0.408 -0.854      
prdp-:_18/19:TRUE -0.003  0.175  0.536  0.296    0.272 -0.189      
                  prdp-:_18/19 prdd-:TRUE prdp-:TRUE s_18/19:
prddrng-lck                                                  
prdpst-lckd                                                  
schl_y18/19                                                  
mcqTRUE                                                      
prdd-:_18/19                                                 
prdp-:_18/19                                                 
prddr-:TRUE        0.183                                     
prdps-:TRUE        0.541        0.307                        
s_18/19:TRU        0.308        0.483      0.303             
prdd-:_18/19:TRUE -0.193       -0.617     -0.190     -0.524  
prdp-:_18/19:TRUE -0.844       -0.204     -0.647     -0.355  
                  prdd-:_18/19:TRUE
prddrng-lck                        
prdpst-lckd                        
schl_y18/19                        
mcqTRUE                            
prdd-:_18/19                       
prdp-:_18/19                       
prddr-:TRUE                        
prdps-:TRUE                        
s_18/19:TRU                        
prdd-:_18/19:TRUE                  
prdp-:_18/19:TRUE  0.220           

Save coefficients as a table for in the paper:

m_acc_coef <- as.data.frame(m_acc_summary$coefficients)
setDT(m_acc_coef, keep.rownames = TRUE)
m_acc_coef$rn <- c("Intercept \\small{(Period: pre-lockdown, School year: 19/20, Question type: open answer)}",
                   "Period: lockdown",
                   "Period: post-lockdown",
                   "School year: 18/19",
                   "Question type: multiple choice",
                   "Period: lockdown $\\times$ School year: 18/19",
                   "Period: post-lockdown $\\times$ School year: 18/19",
                   "Period: lockdown $\\times$ Question type: multiple choice",
                   "Period: post-lockdown $\\times$ Question type: multiple choice",
                   "School year: 18/19 $\\times$ Question type: multiple choice",
                   "Period: lockdown $\\times$ School year: 18/19 $\\times$ Question type: multiple choice",
                   "Period: post-lockdown $\\times$ School year: 18/19 $\\times$ Question type: multiple choice")

# Format p-values
m_acc_coef$`Pr(>|z|)` <- format.pval(m_acc_coef$`Pr(>|z|)`, eps = .001, digits = 3, flag = "0")
m_acc_coef$`Pr(>|z|)` <- sub('^(<)?0[.]', '\\1.', m_acc_coef$`Pr(>|z|)`) # Remove leading zero

cat(knitr::kable(m_acc_coef,
                 align = c("l","r", "r", "r", "r"),
                 digits = c(NA, 3, 3, 2, NA),
                 col.names = c("Effect", "$b$", "SE", "$z$", "$p$"),
                 format = "latex",
                 booktabs = TRUE,
                 escape = FALSE),
    file = "../output/m_acc_table.tex")

Visualise the model fit:

acc_fit <- expand.grid(period = c("pre-lockdown", "during-lockdown", "post-lockdown"), school_year = c("18/19", "19/20"), mcq = c(TRUE, FALSE))
acc_fit <- cbind(acc_fit, accuracy = predict(m_acc, type = "response", re.form = NA, newdata = acc_fit))
acc_fit
ggplot(acc_fit, aes(x = period, y = accuracy, colour = school_year, lty = mcq, group = interaction(mcq, school_year))) +
  geom_line() +
  geom_point() +
  scale_y_continuous(limits = c(.7, 1), labels = scales::percent_format()) +
  theme_paper

Empirical means:

accuracy_mean <- accuracy[(course == "English" & mcq == TRUE) | course == "French", .(accuracy = sum(accuracy * n)/sum(n)), by = .(period, school_year, mcq, user, course)][, .(accuracy = mean(accuracy), accuracy_sd = sd(accuracy)), by = .(period, school_year, mcq)]
accuracy_mean
ggplot(accuracy_mean, aes(x = period, y = accuracy, colour = school_year, lty = mcq, group = interaction(mcq, school_year))) +
  geom_line() +
  geom_point() +
  scale_y_continuous(limits = c(.7, 1), labels = scales::percent_format()) +
  theme_paper

Response time

db <- db_connect()
rt <- dbGetQuery(db, 
                 "SELECT r.method AS 'method',
                  r.book_info_id AS 'book_info_id',
                  DATE(r.date + 3600, 'unixepoch') AS 'doy',
                  r.user_id AS 'user',
                  r.choices > 1 AS 'mcq',
                  r.rt AS 'rt'
                  FROM 'responses_noduplicates' r
                  WHERE r.study == 0
                  AND r.correct == 1
                 "
)
setDT(rt)
db_disconnect(db)
doys <- rt[, .(doy = unique(doy))][, doy_posix := as.POSIXct(doy)][]
doys[, doy_posix_week := cut.POSIXt(as.POSIXct(doy), "week")]
doys[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]
doys[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
doys[school_year == "19/20", doy_posix_aligned := doy_posix]
doys[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
doys[, period := dplyr::case_when(
  doy_posix_aligned >= date_schools_opened ~ "post-lockdown",
  doy_posix_aligned >= date_schools_closed & doy_posix_aligned < date_schools_opened ~ "during-lockdown",
  doy_posix_aligned < date_schools_opened ~ "pre-lockdown"
)]
# Reorder factor levels so that intercept is pre-lockdown in 19/20
doys[, period := factor(period, levels = c("pre-lockdown", "during-lockdown", "post-lockdown"))]
doys[, school_year := factor(school_year, levels = c("19/20", "18/19"))]
rt <- rt[doys, on = "doy"]
rt[, mcq := as.factor(as.logical(mcq))]
rt[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Throw out trials with negative RTs (timing errors)

rt <- rt[rt > 0]

Whole population

rt_med <- rt[, .(rt_median = median(rt)), by = .(school_year, mcq, user, course, doy_posix_week)]

rt_by_week <- rt_med[, .(rt = mean(rt_median), rt_se = sd(rt_median)/sqrt(.N)), by = .(school_year, mcq, course, doy_posix_week)]

Overlap the two school years:

rt_by_week[school_year == "18/19", doy_posix_week_aligned := as.POSIXct(as.POSIXct(doy_posix_week) + 365*24*60*60, origin = "1970-01-01")]
rt_by_week[school_year == "19/20", doy_posix_week_aligned := as.POSIXct(doy_posix_week)]

Add question type labels:

rt_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
rt_by_week[, school_year := factor(school_year, levels = c("18/19", "19/20"))]

Plot response time by week (mean +/- 1 SE).

p_rt <- ggplot(rt_by_week[(course == "English" & mcq == TRUE) | course == "French",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(. ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1.7, 3.7)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt
Warning: Removed 24 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type.pdf", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type.eps", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/rt_by_question_type.png", width = 9, height = 3)
Warning: Removed 24 row(s) containing missing values (geom_path).

By level and year

Add book info:

rt[book_info[book_type == "Hoofdboek",], on  = "book_info_id", c("method_group", "book_title") := .(i.method_group, i.book_title)]

Simplify level names:

# Keep all distinctions
rt[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
rt[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
rt[, level := dplyr::case_when(
  grepl( "hv", book_title) ~ "General secondary\n(havo)",
  grepl("vmbo", book_title) ~ "Pre-vocational\n(vmbo)",
  grepl("havo", book_title) ~ "General secondary\n(havo)",
  grepl("vwo", book_title) ~ "Pre-university\n(vwo)",
  TRUE ~ "Other")]
rt[, level := factor(level, levels = c("Other", "Pre-vocational\n(vmbo)", "General secondary\n(havo)", "Pre-university\n(vwo)"))]

Simplify year names:

rt[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]
rt_strat_med <- rt[, .(rt_median = median(rt)), by = .(school_year, mcq, user, course, level, year, doy_posix_week)]

rt_strat_by_week <- rt_strat_med[, .(rt = mean(rt_median), rt_se = sd(rt_median)/sqrt(.N)), by = .(school_year, mcq, course, level, year, doy_posix_week)]

Overlap the two school years:

rt_strat_by_week[school_year == "18/19", doy_posix_week_aligned := as.POSIXct(as.POSIXct(doy_posix_week) + 365*24*60*60, origin = "1970-01-01")]
rt_strat_by_week[school_year == "19/20", doy_posix_week_aligned := as.POSIXct(doy_posix_week)]

Add question type labels:

rt_strat_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
rt_strat_by_week[, school_year := factor(school_year, levels = c("18/19", "19/20"))]

Plot response time by week (mean +/- 1 SE).

p_rt_level_year <- ggplot(rt_strat_by_week[course == "French",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1, 4)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt_level_year
Warning: Removed 11 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type_french_level_year.pdf", width = 9, height = 3)
Warning: Removed 11 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type_french_level_year.eps", width = 9, height = 3)
Warning: Removed 11 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/rt_by_question_type_french_level_year.png", width = 9, height = 3)
Warning: Removed 11 row(s) containing missing values (geom_path).
p_rt_level_year <- ggplot(rt_strat_by_week[course == "English" & question_type == "Multiple\nchoice" & level != "Other",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ year) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1, 4)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt_level_year
Warning: Removed 5 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type_english_level_year.pdf", width = 9, height = 3)
Warning: Removed 5 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type_english_level_year.eps", width = 9, height = 3)
Warning: Removed 5 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/rt_by_question_type_english_level_year.png", width = 9, height = 3)
Warning: Removed 5 row(s) containing missing values (geom_path).

By level

rt_level_med <- rt[, .(rt_median = median(rt)), by = .(school_year, mcq, user, course, level, doy_posix_week)]

rt_level_by_week <- rt_level_med[, .(rt = mean(rt_median), rt_se = sd(rt_median)/sqrt(.N)), by = .(school_year, mcq, course, level, doy_posix_week)]

Overlap the two school years:

rt_level_by_week[school_year == "18/19", doy_posix_week_aligned := as.POSIXct(as.POSIXct(doy_posix_week) + 365*24*60*60, origin = "1970-01-01")]
rt_level_by_week[school_year == "19/20", doy_posix_week_aligned := as.POSIXct(doy_posix_week)]

Add question type labels:

rt_level_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
rt_level_by_week[, school_year := factor(school_year, levels = c("18/19", "19/20"))]

Plot response time by week (mean +/- 1 SE).

p_rt_level <- ggplot(rt_level_by_week[((course == "English" & question_type == "Multiple\nchoice") | course == "French") & level != "Other",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(level ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1, 6)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt_level
Warning: Removed 20 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type_level.pdf", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type_level.eps", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/rt_by_question_type_level.png", width = 9, height = 5)
Warning: Removed 20 row(s) containing missing values (geom_path).

By year

rt_year_med <- rt[, .(rt_median = median(rt)), by = .(school_year, mcq, user, course, year, doy_posix_week)]

rt_year_by_week <- rt_year_med[, .(rt = mean(rt_median), rt_se = sd(rt_median)/sqrt(.N)), by = .(school_year, mcq, course, year, doy_posix_week)]

Overlap the two school years:

rt_year_by_week[school_year == "18/19", doy_posix_week_aligned := as.POSIXct(as.POSIXct(doy_posix_week) + 365*24*60*60, origin = "1970-01-01")]
rt_year_by_week[school_year == "19/20", doy_posix_week_aligned := as.POSIXct(doy_posix_week)]

Add question type labels:

rt_year_by_week[, question_type := ifelse(mcq == TRUE, "Multiple\nchoice", "Open\nanswer")]
rt_year_by_week[, school_year := factor(school_year, levels = c("18/19", "19/20"))]

Plot response time by week (mean +/- 1 SE).

p_rt_year <- ggplot(rt_year_by_week[((course == "English" & question_type == "Multiple\nchoice") | course == "French") & year != "Other",],
            aes(x = doy_posix_week_aligned, y = rt/1e3, group = interaction(school_year, question_type), colour = school_year, fill = school_year)) +
  facet_grid(year ~ course) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = 0, ymax = 1000, fill = "grey92", colour = "grey50", lty = 2, alpha = .9) +
  geom_ribbon(aes(ymin = rt/1e3 - rt_se/1e3, ymax = rt/1e3 + rt_se/1e3, colour = NULL), alpha = 0.2) +
  geom_line(aes(lty = question_type)) +
  scale_x_datetime(expand = c(0, 0),
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::unit_format(unit = "s", accuracy = .1)) +
  coord_cartesian(ylim = c(1, 4)) +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  scale_fill_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Response time",
       colour = "School year",
       fill = "School year",
       lty = "Question type") +
  guides(colour = guide_legend(order = 1),
         fill = guide_legend(order = 1),
         lty = guide_legend(order = 2)) +
  theme_paper

p_rt_year
Warning: Removed 14 row(s) containing missing values (geom_path).

ggsave("../output/rt_by_question_type_year.pdf", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).
ggsave("../output/rt_by_question_type_year.eps", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/rt_by_question_type_year.png", width = 9, height = 5)
Warning: Removed 14 row(s) containing missing values (geom_path).

Regression model

rt_model_dat <- rt[, .(rt_median = median(rt)), by = .(course, school_year, period, doy_posix, mcq, user)]

Fit a generalised linear mixed effects model (assuming a Gamma distribution for RT and an identity link function; Lo & Andrew, 2015) to the daily median RT:

if(!file.exists("../output/m_rt_fit.rds")) {
  m_rt <- glmer(rt_median ~ period*school_year*mcq + (1 | user) + (1 | course),
                 data = rt_model_dat[(course == "English" & mcq == TRUE) | course == "French",],
                 family = Gamma(link = "identity"),
                 nAGQ = 0,
                 control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 1e6)))
  saveRDS(m_rt, "../output/m_rt_fit.rds")
} else {
  m_rt <- readRDS("../output/m_rt_fit.rds")
}

m_rt_summary <- summary(m_rt)
m_rt_summary
Generalized linear mixed model fit by maximum likelihood (Adaptive
  Gauss-Hermite Quadrature, nAGQ = 0) [glmerMod]
 Family: Gamma  ( identity )
Formula: 
rt_median ~ period * school_year * mcq + (1 | user) + (1 | course)
   Data: rt_model_dat[(course == "English" & mcq == TRUE) | course ==  
    "French", ]
Control: 
glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 1e+06))

     AIC      BIC   logLik deviance df.resid 
10294902 10295073 -5147436 10294872   666488 

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
 -2.667  -0.337  -0.069   0.235 205.150 

Random effects:
 Groups   Name        Variance  Std.Dev.
 user     (Intercept) 2.935e+05 541.7736
 course   (Intercept) 2.753e+02  16.5917
 Residual             1.390e-01   0.3728
Number of obs: 666503, groups:  user, 133398; course, 2

Fixed effects:
                                               Estimate Std. Error t value
(Intercept)                                    2134.021     12.889 165.574
periodduring-lockdown                           208.799      7.148  29.209
periodpost-lockdown                             197.658     11.085  17.831
school_year18/19                                 27.350      6.744   4.055
mcqTRUE                                         150.272      5.544  27.108
periodduring-lockdown:school_year18/19         -281.173     11.675 -24.084
periodpost-lockdown:school_year18/19           -293.256     17.615 -16.648
periodduring-lockdown:mcqTRUE                  -211.632      7.688 -27.526
periodpost-lockdown:mcqTRUE                    -200.943     12.213 -16.454
school_year18/19:mcqTRUE                        -61.876      7.084  -8.734
periodduring-lockdown:school_year18/19:mcqTRUE  279.138     12.590  22.171
periodpost-lockdown:school_year18/19:mcqTRUE    316.380     19.331  16.366
                                               Pr(>|z|)    
(Intercept)                                     < 2e-16 ***
periodduring-lockdown                           < 2e-16 ***
periodpost-lockdown                             < 2e-16 ***
school_year18/19                               5.01e-05 ***
mcqTRUE                                         < 2e-16 ***
periodduring-lockdown:school_year18/19          < 2e-16 ***
periodpost-lockdown:school_year18/19            < 2e-16 ***
periodduring-lockdown:mcqTRUE                   < 2e-16 ***
periodpost-lockdown:mcqTRUE                     < 2e-16 ***
school_year18/19:mcqTRUE                        < 2e-16 ***
periodduring-lockdown:school_year18/19:mcqTRUE  < 2e-16 ***
periodpost-lockdown:school_year18/19:mcqTRUE    < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
                  (Intr) prddr- prdps- sc_18/19 mcTRUE prdd-:_18/19
prddrng-lck       -0.261                                           
prdpst-lckd       -0.165  0.330                                    
schl_y18/19       -0.289  0.484  0.308                             
mcqTRUE           -0.349  0.567  0.363  0.621                      
prdd-:_18/19       0.166 -0.619 -0.204 -0.533   -0.354             
prdp-:_18/19       0.109 -0.214 -0.633 -0.354   -0.234  0.228      
prddr-:TRUE        0.236 -0.862 -0.278 -0.436   -0.651  0.532      
prdps-:TRUE        0.147 -0.272 -0.856 -0.273   -0.408  0.168      
s_18/19:TRU        0.256 -0.441 -0.281 -0.880   -0.705  0.484      
prdd-:_18/19:TRUE -0.148  0.531  0.171  0.475    0.404 -0.883      
prdp-:_18/19:TRUE -0.096  0.176  0.543  0.310    0.263 -0.191      
                  prdp-:_18/19 prdd-:TRUE prdp-:TRUE s_18/19:
prddrng-lck                                                  
prdpst-lckd                                                  
schl_y18/19                                                  
mcqTRUE                                                      
prdd-:_18/19                                                 
prdp-:_18/19                                                 
prddr-:TRUE        0.179                                     
prdps-:TRUE        0.541        0.301                        
s_18/19:TRU        0.321        0.491      0.307             
prdd-:_18/19:TRUE -0.194       -0.615     -0.186     -0.539  
prdp-:_18/19:TRUE -0.873       -0.194     -0.634     -0.353  
                  prdd-:_18/19:TRUE
prddrng-lck                        
prdpst-lckd                        
schl_y18/19                        
mcqTRUE                            
prdd-:_18/19                       
prdp-:_18/19                       
prddr-:TRUE                        
prdps-:TRUE                        
s_18/19:TRU                        
prdd-:_18/19:TRUE                  
prdp-:_18/19:TRUE  0.212           

Save coefficients as a table for in the paper:

m_rt_coef <- as.data.frame(m_rt_summary$coefficients)
setDT(m_rt_coef, keep.rownames = TRUE)
m_rt_coef$rn <- c("Intercept \\small{(Period: pre-lockdown, School year: 19/20, Question type: open answer)}",
                   "Period: lockdown",
                   "Period: post-lockdown",
                   "School year: 18/19",
                   "Question type: multiple choice",
                   "Period: lockdown $\\times$ School year: 18/19",
                   "Period: post-lockdown $\\times$ School year: 18/19",
                   "Period: lockdown $\\times$ Question type: multiple choice",
                   "Period: post-lockdown $\\times$ Question type: multiple choice",
                   "School year: 18/19 $\\times$ Question type: multiple choice",
                   "Period: lockdown $\\times$ School year: 18/19 $\\times$ Question type: multiple choice",
                   "Period: post-lockdown $\\times$ School year: 18/19 $\\times$ Question type: multiple choice")

# Format p-values
m_rt_coef$`Pr(>|z|)` <- format.pval(m_rt_coef$`Pr(>|z|)`, eps = .001, digits = 3, flag = "0")
m_rt_coef$`Pr(>|z|)` <- sub('^(<)?0[.]', '\\1.', m_rt_coef$`Pr(>|z|)`) # Remove leading zero

cat(knitr::kable(m_rt_coef,
                 align = c("l","r", "r", "r", "r"),
                 digits = c(NA, 3, 3, 2, NA),
                 col.names = c("Effect", "$b$", "SE", "$z$", "$p$"),
                 format = "latex",
                 booktabs = TRUE,
                 escape = FALSE),
    file = "../output/m_rt_table.tex")

Visualise the model fit:

rt_fit <- expand.grid(period = c("pre-lockdown", "during-lockdown", "post-lockdown"), school_year = c("18/19", "19/20"), mcq = c(TRUE, FALSE))
rt_fit <- cbind(rt_fit, rt = predict(m_rt, type = "response", re.form = NA, newdata = rt_fit))
rt_fit
ggplot(rt_fit, aes(x = period, y = rt, colour = school_year, lty = mcq, group = interaction(mcq, school_year))) +
  geom_line() +
  geom_point() +
  scale_y_continuous(limits = c(1500, 4000)) +
  theme_paper

Empirical means:

rt_mean <- rt_model_dat[(course == "English" & mcq == TRUE) | course == "French", .(rt = mean(rt_median)), by = .(period, school_year, mcq, user, course)][, .(rt = mean(rt), rt_sd = sd(rt)), by = .(period, school_year, mcq, course)]
rt_mean[, school_year := factor(school_year, levels = c("18/19", "19/20"))]
rt_mean
ggplot(rt_mean, aes(x = period, y = rt, colour = school_year, lty = mcq, group = interaction(mcq, school_year))) +
  facet_grid(~ course) +
  geom_line() +
  geom_point() +
  scale_y_continuous(limits = c(1500, 3000)) +
  theme_paper

Combination plot

p_legend <- get_legend(p_acc)
Warning: Removed 24 row(s) containing missing values (geom_path).
p_acc <- p_acc +
  guides(colour = FALSE, fill = FALSE, lty = FALSE)

p_rt <- p_rt +
  guides(colour = FALSE, fill = FALSE, lty = FALSE)

Combine plots:

plot_grid(
  plot_grid(p_acc, p_rt,
            ncol = 1,
            labels = c("A", "B")),
  p_legend,
  rel_widths = c(1, .2)
)
Warning: Removed 24 row(s) containing missing values (geom_path).

Warning: Removed 24 row(s) containing missing values (geom_path).

ggsave("../output/combi_acc_rt.pdf", width = 9, height = 3.5)
ggsave("../output/combi_acc_rt.eps", width = 9, height = 3.5)
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/combi_acc_rt.png", width = 9, height = 3.5)

Learning progress

Get the unique book chapter IDs on each day:

db <- db_connect()

progress <- dbGetQuery(db,
                       "SELECT DISTINCT r.book_info_id AS 'book_info_id',
                        r.method AS 'method',
                        DATE(r.date + 3600, 'unixepoch') AS 'doy',
                        COUNT(*) AS 'trials'
                        FROM 'responses_noduplicates' r
                        GROUP BY r.method,
                        r.book_info_id,
                        DATE(r.date + 3600, 'unixepoch');"
                       )

db_disconnect(db)

setDT(progress)

Join with the book chapter information:

db <- db_connect()
book_info <- dbGetQuery(db, "SELECT DISTINCT * FROM 'book_info'")
db_disconnect(db)

setDT(book_info)
progress[book_info[book_type == "Hoofdboek",], on  = "book_info_id", c("method_group", "book_title", "chapter") := .(i.method_group, i.book_title, i.chapter)]

Add sensible course names:

progress[, course := ifelse(method == "Grandes Lignes", "French", ifelse(method == "Stepping Stones", "English", "German"))]

Add a school year column (cutoff date: 1 August):

progress[, doy_posix := as.POSIXct(doy)]
progress[, school_year := ifelse(doy_posix < "2019-08-01", "18/19", "19/20")]

Consolidate count by day and chapter:

progress_by_day <- progress[, .(trials = sum(trials)), by = .(school_year, doy_posix, course, method_group, book_title, chapter)]

Simplify level names:

# Keep all distinctions
progress_by_day[, book_title_simple := stringr::str_sub(book_title, 3, -10)]
progress_by_day[, book_title_simple := factor(book_title_simple, levels = c("vmbo b/lwoo", "vmbo b", "vmbo bk", "vmbo k", "vmbo kgt", "vmbo-gt", "vmbo gt", "vmbo-gt/havo", "vmbo (t)hv", "havo", "havo vwo", "vwo"))]
# Simplify to three levels
progress_by_day[, level := dplyr::case_when(
  grepl( "hv", book_title) ~ "General secondary (havo)",
  grepl("vmbo", book_title) ~ "Pre-vocational (vmbo)",
  grepl("havo", book_title) ~ "General secondary (havo)",
  grepl("vwo", book_title) ~ "Pre-university (vwo)",
  TRUE ~ "Other")]
progress_by_day[, level := factor(level, levels = c("Other", "Pre-vocational (vmbo)", "General secondary (havo)", "Pre-university (vwo)"))]

Simplify year names:

progress_by_day[, year := dplyr::case_when(
  method_group == "Leerjaar 1 (5e Ed.)" ~ "Year 1",
  method_group == "Leerjaar 2 (5e Ed.)" ~ "Year 2",
  method_group == "Leerjaar 3 (5e Ed.)" ~ "Year 3",
  method_group == "Leerjaar 3/4 (5e Ed.)" ~ "Year 3/4",
  method_group == "Leerjaar 4 (5e Ed.)" ~ "Year 4",
  method_group == "Tweede Fase (6e Ed.)" ~ "Tweede Fase",
  TRUE ~ "Other")]

Simplify chapter names:

# In most cases, the chapter name starts with a number
progress_by_day[, chapter_simple := factor(as.numeric(stringr::str_extract(chapter, "^\\d{1,2}")))]
# Remaining cases:
unique(progress_by_day[is.na(chapter_simple),]$chapter)
 [1] "BS2 Dienstleistung"               
 [2] "BS5 Reisen"                       
 [3] "BS1 Familie und Beziehungen"      
 [4] "BS3 Dienstleistung"               
 [5] "BS4 Reisen und Verkehr"           
 [6] "Lernliste Brückenschlag"          
 [7] "BS2 Freizeit"                     
 [8] "BS1 Schule und Ausbildung"        
 [9] "Bridging the Gap Year 2"          
[10] "Bridging the Gap Year 1"          
[11] "Bridging the Gap Exam Preparation"
[12] "Exam Preparation"                 
[13] "Bridging the Gap mbo"             
[14] "Bridging the Gap havo"            
BS2 Dienstleistung

BS5 Reisen

BS1 Familie und Beziehungen

BS3 Dienstleistung

BS4 Reisen und Verkehr

Lernliste Brückenschlag

BS2 Freizeit

BS1 Schule und Ausbildung

Bridging the Gap Year 2

Bridging the Gap Year 1

Bridging the Gap Exam Preparation

Exam Preparation

Bridging the Gap mbo

Bridging the Gap havo
# Combine these chapters into an "other" category
progress_by_day[is.na(chapter_simple), chapter_simple := "O"]

Align school years:

progress_by_day[school_year == "18/19", doy_posix_aligned := as.POSIXct(doy_posix + 365*24*60*60, origin = "1970-01-01")]
progress_by_day[school_year == "19/20", doy_posix_aligned := doy_posix]

Use cut.Date() to bin dates by week and month. Each day is assigned the date of the most recent Monday.

progress_by_day[, doy_posix_aligned_week := cut.POSIXt(doy_posix_aligned, "week")]
progress_by_day[, doy_posix_aligned_month := cut.POSIXt(doy_posix_aligned, "month")]

Calculate proportions by week and month:

progress_by_week <- progress_by_day[, .(trials = sum(trials)), by = .(school_year, doy_posix_aligned_week, course, level, year, chapter_simple)]
progress_by_week[, prop := trials/sum(trials), by = .(school_year, doy_posix_aligned_week, course, level, year)]
progress_by_month <- progress_by_day[, .(trials = sum(trials)), by = .(school_year, doy_posix_aligned_month, course, level, year, chapter_simple)]
progress_by_month[, prop := trials/sum(trials), by = .(school_year, doy_posix_aligned_month, course, level, year)]
setorder(progress_by_week, chapter_simple)
setorder(progress_by_month, chapter_simple)

French

p_french_y1 <- ggplot(progress_by_week[course == "French" & year == "Year 1"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_french_y2 <- ggplot(progress_by_week[course == "French" & year == "Year 2"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_french_y3 <- ggplot(progress_by_week[course == "French" & year == "Year 3/4"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_progress_french <- plot_grid(p_french_y1, p_french_y2, p_french_y3, 
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c("Year 1", "Year 2", "Year 3/4"),
          hjust = -0.1,
          scale = .95)
Warning: Removed 48 rows containing missing values (position_stack).
Warning: Removed 40 rows containing missing values (geom_col).
Warning: Removed 46 rows containing missing values (position_stack).
Warning: Removed 41 rows containing missing values (geom_col).
Warning: Removed 24 rows containing missing values (position_stack).
Warning: Removed 23 rows containing missing values (geom_col).
p_progress_french

ggsave("../output/progress_french.pdf", width = 9, height = 9)
ggsave("../output/progress_french.eps", width = 9, height = 9)
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/progress_french.png", width = 9, height = 6)

Did the share of trials change between school years? We can simplify the analysis by aggregating over the whole lockdown period.

progress_lockdown <- progress_by_day[between(doy_posix_aligned, date_schools_closed, date_schools_opened), .(trials = sum(trials)), by = .(school_year, course, level, year, chapter_simple)]

# Fill in missing rows (occurs when chapter was only studied in one of the two years)
progress_lockdown <- as.data.table(tidyr::complete(progress_lockdown, tidyr::nesting(course, level, year, chapter_simple), school_year, fill = list(trials = 0))) 
  
progress_lockdown[, prop := trials/sum(trials), by = .(school_year, course, level, year)]
setorder(progress_lockdown, chapter_simple)
ggplot(progress_lockdown[course == "French"], aes(x = school_year, y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(level ~ year) +
  geom_col(colour = NA) +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter",
       title = "French") +
  theme_paper

Perform a chi-square test of homogeneity to determine whether school years are significantly different.

for (y in sort(unique(progress_lockdown$year))) {
  for (l in levels(progress_lockdown$level)) {
    d <- progress_lockdown[course == "French" & year == y & level == l]
    if (nrow(d) > 0) {
      print(paste("French", y, l, collapse= " "))
      print(
        chisq.test(
          dcast(d, school_year ~ chapter_simple, value.var = "trials", fill = 0)[, school_year := NULL]
        )
      )
    }
  } 
}
[1] "French Year 1 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 26799, df = 5, p-value < 2.2e-16

[1] "French Year 1 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 229477, df = 5, p-value < 2.2e-16

[1] "French Year 1 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 40312, df = 5, p-value < 2.2e-16

[1] "French Year 2 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 13882, df = 5, p-value < 2.2e-16

[1] "French Year 2 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 50486, df = 5, p-value < 2.2e-16

[1] "French Year 2 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 46882, df = 5, p-value < 2.2e-16

[1] "French Year 3/4 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 30269, df = 7, p-value < 2.2e-16

[1] "French Year 3/4 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 23169, df = 5, p-value < 2.2e-16

[1] "French Year 3/4 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 21545, df = 5, p-value < 2.2e-16

Conclusion: all tests indicate a difference in proportions between school years (p << 0.001).

Visualise the change between school years:

progress_lockdown[, prop_change := prop[school_year == "19/20"] - prop[school_year == "18/19"], by = .(course, level, year, chapter_simple)]
ggplot(progress_lockdown[school_year == "19/20" & course == "French"], aes(x = chapter_simple, y = (prop_change * 100), colour = chapter_simple, group = school_year)) +
  facet_grid(level ~ year, scales = "free_x") +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(xend = chapter_simple), yend = 0) +
  geom_point() +
  scale_y_continuous(limits = c(-25, 25)) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = "Chapter",
       y = "Change in trial share\n(percentage points)",
       title = "French") +
  theme_paper

Are these changes really important? We may expect a certain amount of fluctuation between any pair of school years. We don’t have data from before the 18/19 school year, but we can look at how the magnitude of changes during the lockdown period compares to changes earlier in the school year.

To keep things as comparable as possible, use a sliding time window with the same size as the lockdown period:

time_window <- as.numeric(round(date_schools_opened - date_schools_closed))
time_window
[1] 78
date_range <- sort(unique(progress_by_day$doy_posix_aligned))
date_range <- date_range[date_range < date_schools_closed]

prop_change_window <- data.table()

for (i in 1:(length(date_range) - as.numeric(time_window))) {
  d <- date_range[i:(i + time_window - 1)]
  progress_window <- progress_by_day[course %in% c("French", "English") & doy_posix_aligned %in% d,
                                     .(trials = sum(trials)),
                                     by = .(school_year, course, level, year, chapter_simple)]
  
  # Fill in missing rows (occurs when chapter was only studied in one of the two years)
  progress_window <- as.data.table(tidyr::complete(progress_window, tidyr::nesting(course, level, year, chapter_simple), school_year, fill = list(trials = 0))) 
  
  progress_window[, prop := trials/sum(trials), by = .(school_year, course, level, year)]

  progress_window[, prop_change := prop[school_year == "19/20"] - prop[school_year == "18/19"], by = .(course, level, year, chapter_simple)]
  
  prop_change_window <- rbind(prop_change_window, progress_window[school_year == "19/20",][,window := i][,date_min := min(d)][,date_max := max(d)])
}

The density of year-to-year changes can be visualised by time window:

ggplot(prop_change_window, aes(x = prop_change * 100, y = window, group = window)) +
  ggridges::geom_density_ridges(alpha = 0.1, scale = 25, fill = NA) +
  labs(x = "Change in trial share\n(percentage points)",
         y = "Time window") +
  theme_paper
Picking joint bandwidth of 0.584

Compare the aggregated density to the changes during the lockdown period:

prop_change_combined <- rbind(prop_change_window[, period := "Pre-lockdown"], progress_lockdown[course %in% c("French", "English") & school_year == "19/20", period := "Lockdown"], fill = TRUE)
prop_change_combined[, period := factor(period, levels = c("Pre-lockdown", "Lockdown"))]
ggplot(prop_change_combined, aes(x = prop_change, colour = period)) +
  geom_density() +
  scale_colour_viridis_d(end = .5, direction = -1, na.translate = FALSE) +
  labs(x = "Change in trial share\n(percentage points)",
       y = "Density",
       colour = NULL) +
  theme_paper

prop_change_sd <- prop_change_window[, .(sd = sd(prop_change) * 100), by = .(course, year, level)]

Add boundaries based on the typical spread to the change plot:

p_change_french <- ggplot(progress_lockdown[school_year == "19/20" & course == "French"], aes(colour = chapter_simple)) +
  facet_grid(year ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "French"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "French"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100))) +
  scale_y_continuous(breaks = c(-20, 0, 20)) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = "Chapter",
       y = "Change in trial share\n(percentage points)") +
  theme_paper +
  theme(panel.grid.major.y = element_blank())

p_change_french

ggsave("../output/progress_change_french.pdf", width = 5, height = 4)
ggsave("../output/progress_change_french.eps", width = 5, height = 4)
ggsave("../output/progress_change_french.png", width = 9, height = 3)

Make a combination plot for in the paper:

plot_grid(p_french_y1, p_french_y2, p_french_y3, p_change_french,
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c("Year 1", "Year 2", "Year 3/4", "Change"),
          rel_heights = c(1, 1, 1, 1.5),
          hjust = -0.1,
          scale = .95)
Warning: Removed 48 rows containing missing values (position_stack).
Warning: Removed 40 rows containing missing values (geom_col).
Warning: Removed 46 rows containing missing values (position_stack).
Warning: Removed 41 rows containing missing values (geom_col).
Warning: Removed 24 rows containing missing values (position_stack).
Warning: Removed 23 rows containing missing values (geom_col).

ggsave("../output/progress_combi_french.pdf", width = 9, height = 9)
ggsave("../output/progress_combi_french.eps", width = 9, height = 9)
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/progress_combi_french.png", width = 9, height = 9)
p_change_french_y1 <- ggplot(progress_lockdown[school_year == "19/20" & course == "French" & year == "Year 1"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 1"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 1"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-20, 0, 20), limits = c(-30, 30), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_french_y2 <- ggplot(progress_lockdown[school_year == "19/20" & course == "French" & year == "Year 2"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 2"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 2"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-20, 0, 20),  limits = c(-30, 30), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_french_y3 <- ggplot(progress_lockdown[school_year == "19/20" & course == "French" & year == "Year 3/4"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 3/4"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "French" & year == "Year 3/4"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-20, 0, 20),  limits = c(-30, 30), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

filler_plot <- qplot() + 
  theme_nothing() + 
  theme(panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

plot_grid(
          filler_plot,
          p_french_y1, filler_plot, p_change_french_y1, filler_plot,
          p_french_y2, filler_plot, p_change_french_y2, filler_plot, 
          p_french_y3, filler_plot, p_change_french_y3,
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c(NA,
                     "Year 1", NA, NA, NA,
                     "Year 2", NA, NA, NA,
                     "Year 3/4", NA, NA),
          rel_heights = c(.1, 
                          1, -.2, .75, .1,
                          1, -.2, .75, .1,
                          1, -.2, .75),
          hjust = -0.1,
          vjust = -0.1,
          scale = .95)
Warning: Removed 48 rows containing missing values (position_stack).
Warning: Removed 40 rows containing missing values (geom_col).
Warning: Removed 46 rows containing missing values (position_stack).
Warning: Removed 41 rows containing missing values (geom_col).
Warning: Removed 24 rows containing missing values (position_stack).
Warning: Removed 23 rows containing missing values (geom_col).
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

ggsave("../output/progress_combi_alt_french.pdf", width = 9, height = 9)
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).
ggsave("../output/progress_combi_alt_french.eps", width = 9, height = 9)
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/progress_combi_alt_french.png", width = 9, height = 9)
Warning: Removed 1 rows containing missing values (geom_text).
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Make a table of the changes:

progress_table <- progress_lockdown[school_year == "19/20", .(course, year, level, chapter_simple, prop_change)][prop_change_sd, on = .(course, year, level)]

progress_table <- progress_table[level != "Other"][, .(Course = course, Year = sub(year, pattern = "Year ", replacement = ""), Level = ifelse(grepl("vmbo", level), "vmbo", ifelse(grepl("havo", level), "havo", "vwo")), `Baseline (1 SD)` = scales::percent(sd/100, accuracy = .01, suffix = ""), Chapter = chapter_simple, Change = scales::percent(prop_change, accuracy = .01, prefix = ifelse(prop_change > 0, "+", ""), suffix = ""))]

progress_table <- tidyr::pivot_wider(progress_table, Course:`Baseline (1 SD)`, names_from = Chapter, names_prefix = "Ch. ", values_from = Change)

setorder(progress_table, -Course, Year)
progress_table
options(knitr.kable.NA = "")
progress_table_tex <- knitr::kable(progress_table,
                                   align = c("l", "l", "l", rep("r", 12)),
                                   row.names = FALSE,
                                   format = "latex",
                                   booktabs = TRUE,
                                   escape = FALSE)

progress_table_tex <- kableExtra::collapse_rows(progress_table_tex, 
                                                columns = 1:2, 
                                                valign = "top",
                                                latex_hline = "major")

cat(progress_table_tex, file = "../output/progress_table.tex")

English

NOTE: chapters without a number (Bridging the Gap, Exam Preparation) are shown as “O” in the plot. They don’t seem to fit neatly in the chapter sequence, so I’m grouping them together.

p_english_y1 <- ggplot(progress_by_week[level != "Other"][, level := factor(level)][course == "English" & year == "Year 1"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level, drop = FALSE) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_english_y2 <- ggplot(progress_by_week[level != "Other"][, level := factor(level)][course == "English" & year == "Year 2"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level, drop = FALSE) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_english_y3 <- ggplot(progress_by_week[level != "Other"][, level := factor(level)][course == "English" & year == "Year 3"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level, drop = FALSE) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_english_y4 <- ggplot(progress_by_week[level != "Other"][, level := factor(level)][course == "English" & year == "Year 4"], aes(x = as.POSIXct(doy_posix_aligned_week), y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(school_year ~ level, drop = FALSE) +
  geom_col(alpha = 0.75, width = 7*24*60*60, colour = NA) +
  geom_rect(xmin = date_schools_closed, xmax = date_schools_opened, ymin = -0.01, ymax = 1.01, fill = NA, colour = "black", lty = 2) +
  scale_x_datetime(expand = c(0, 0), 
                   breaks = as.POSIXct(c(
                     "2019-10-01 02:00:00 CET",
                     "2019-12-01 02:00:00 CET",
                     "2020-02-01 02:00:00 CET",
                     "2020-04-01 02:00:00 CET",
                     "2020-06-01 02:00:00 CET")),
                   limits = as.POSIXct(c("2019-09-01 02:00:00 CET", "2020-07-01 02:00:00 CET")),
                   date_labels = "%b") +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1), breaks = c(0, .5 , 1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  guides(fill = guide_legend(ncol = 2)) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter") +
  theme_paper

p_progress_english <- plot_grid(p_english_y1, p_english_y2, p_english_y3, p_english_y4,
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c("Year 1", "Year 2", "Year 3", "Year 4"),
          hjust = -0.1,
          scale = .95)
Warning: Removed 61 rows containing missing values (position_stack).
Warning: Removed 61 rows containing missing values (geom_col).
Warning: Removed 69 rows containing missing values (position_stack).
Warning: Removed 60 rows containing missing values (geom_col).
Warning: Removed 49 rows containing missing values (position_stack).
Warning: Removed 50 rows containing missing values (geom_col).
Warning: Removed 7 rows containing missing values (position_stack).
Warning: Removed 4 rows containing missing values (geom_col).
p_progress_english

ggsave("../output/progress_english.pdf", width = 9, height = 9)
ggsave("../output/progress_english.eps", width = 9, height = 9)
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/progress_english.png", width = 9, height = 9)

Did the share of trials change between school years?

ggplot(progress_lockdown[course == "English" & level != "Other"], aes(x = school_year, y = prop, fill = chapter_simple, group = school_year)) +
  facet_grid(level ~ year) +
  geom_col(colour = NA) +
  scale_y_continuous(labels = scales::percent_format(), limits = c(0,1)) +
  scale_fill_viridis_d(direction = -1, na.translate = FALSE) +
  labs(x = NULL,
       y = "Share of trials",
       fill = "Chapter",
       title = "English") +
  theme_paper

Change between school years:

p_change_english <- ggplot(progress_lockdown[school_year == "19/20" & course == "English" & level != "Other"], aes(colour = chapter_simple)) +
  facet_grid(year ~ level, scales = "free_x") +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other"], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other"], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100))) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = "Chapter",
       y = "Change in trial share\n(percentage points)") +
  theme_paper +
  theme(panel.grid.major.y = element_blank())

p_change_english 

ggsave("../output/progress_change_english.pdf", width = 9, height = 6)
ggsave("../output/progress_change_english.eps", width = 9, height = 6)
ggsave("../output/progress_change_english.png", width = 9, height = 6)

Perform a chi-square test of homogeneity to determine whether school years are significantly different.

for (y in sort(unique(progress_lockdown$year))) {
  for (l in levels(progress_lockdown$level)) {
    d <- progress_lockdown[course == "English" & year == y & level == l]
    if (nrow(d) > 0) {
      print(paste("English", y, l, collapse= " "))
      print(
        chisq.test(
          dcast(d, school_year ~ chapter_simple, value.var = "trials", fill = 0)[, school_year := NULL]
        )
      )
    }
  } 
}
[1] "English Year 1 Other"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 1902.8, df = 7, p-value < 2.2e-16

[1] "English Year 1 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 59650, df = 7, p-value < 2.2e-16

[1] "English Year 1 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 236327, df = 7, p-value < 2.2e-16

[1] "English Year 1 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 47589, df = 7, p-value < 2.2e-16

[1] "English Year 2 Other"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 4117.3, df = 7, p-value < 2.2e-16

[1] "English Year 2 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 26122, df = 8, p-value < 2.2e-16

[1] "English Year 2 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 16303, df = 8, p-value < 2.2e-16

[1] "English Year 2 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 84027, df = 7, p-value < 2.2e-16

[1] "English Year 3 Other"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 16118, df = 6, p-value < 2.2e-16

[1] "English Year 3 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 20169, df = 6, p-value < 2.2e-16

[1] "English Year 3 General secondary (havo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 60993, df = 8, p-value < 2.2e-16

[1] "English Year 3 Pre-university (vwo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 41120, df = 7, p-value < 2.2e-16

[1] "English Year 4 Pre-vocational (vmbo)"

    Pearson's Chi-squared test

data:  dcast(d, school_year ~ chapter_simple, value.var = "trials",     fill = 0)[, `:=`(school_year, NULL)]
X-squared = 20187, df = 5, p-value < 2.2e-16

Conclusion: all tests indicate a difference in proportions between school years (p << 0.001).

Make a combination plot for in the paper:

progress_lockdown_english <- progress_lockdown[level != "Other" & school_year == "19/20" & course == "English"]
progress_lockdown_english[, level := factor(level)]
p_change_english_y1 <- ggplot(progress_lockdown_english[year == "Year 1"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x", drop = FALSE) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 1"][,level := factor(level)], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 1"][,level := factor(level)], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-10, 0, 10), limits = c(-20, 20), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_english_y2 <- ggplot(progress_lockdown_english[year == "Year 2"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x", drop = FALSE) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 2"][,level := factor(level)], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 2"][,level := factor(level)], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-10, 0, 10), limits = c(-20, 20), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_english_y3 <- ggplot(progress_lockdown_english[year == "Year 3"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x", drop = FALSE) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 3"][,level := factor(level)], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 3"][,level := factor(level)], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-10, 0, 10), limits = c(-20, 20), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

p_change_english_y4 <- ggplot(progress_lockdown_english[year == "Year 4"], aes(colour = chapter_simple)) +
  facet_grid(. ~ level, scales = "free_x", drop = FALSE) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 4"][,level := factor(level)], aes(ymin = -2*sd, ymax = 2*sd), xmin = 0, xmax = 1000, fill = "grey90", colour = NA) +
  geom_rect(data = prop_change_sd[course == "English" & level != "Other" & year == "Year 4"][,level := factor(level)], aes(ymin = -sd, ymax = sd), xmin = 0, xmax = 100, fill = "grey75", colour = NA) +
  geom_hline(yintercept = 0, lty = 2) +
  geom_segment(aes(x = chapter_simple, xend = chapter_simple, y = (prop_change * 100)), yend = 0, alpha = .75) +
  geom_point(aes(x = chapter_simple, y = (prop_change * 100)), alpha = .75) +
  scale_y_continuous(breaks = c(-10, 0, 10), limits = c(-20, 20), labels = scales::number_format(suffix = " pp")) +
  scale_colour_viridis_d(direction = -1, na.translate = FALSE) +
  guides(colour = FALSE) +
  labs(x = NULL,
       y = "Change") +
  theme_paper +
  theme(panel.grid.major.y = element_blank(),
        strip.background = element_blank(),
        strip.text.x = element_blank(),
        panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

filler_plot <- qplot() + 
  theme_nothing() + 
  theme(panel.background = element_rect(fill = "transparent", colour = NA),
        plot.background = element_rect(fill = "transparent", colour = NA))

plot_grid(
          filler_plot,
          p_english_y1, filler_plot, p_change_english_y1, filler_plot,
          p_english_y2, filler_plot, p_change_english_y2, filler_plot, 
          p_english_y3, filler_plot, p_change_english_y3, filler_plot,
          p_english_y4, filler_plot, p_change_english_y4,
          ncol = 1,
          align = "hv", axis = "tblr",
          labels = c(NA,
                     "Year 1", NA, NA, NA,
                     "Year 2", NA, NA, NA,
                     "Year 3", NA, NA, NA,
                     "Year 4", NA, NA),
          rel_heights = c(.1, 
                          1, -.2, .75, .1,
                          1, -.2, .75, .1,
                          1, -.2, .75, .1,
                          1, -.2, .75),
          hjust = -0.1,
          vjust = -0.1,
          scale = .95)
Warning: Removed 61 rows containing missing values (position_stack).
Warning: Removed 61 rows containing missing values (geom_col).
Warning: Removed 69 rows containing missing values (position_stack).
Warning: Removed 60 rows containing missing values (geom_col).
Warning: Removed 49 rows containing missing values (position_stack).
Warning: Removed 50 rows containing missing values (geom_col).
Warning: Removed 7 rows containing missing values (position_stack).
Warning: Removed 4 rows containing missing values (geom_col).
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

ggsave("../output/progress_combi_alt_english.pdf", width = 9, height = 11)
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).
ggsave("../output/progress_combi_alt_english.eps", width = 9, height = 11)
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).
Warning in grid.Call.graphics(C_rect, x$x, x$y, x$width, x$height,
resolveHJust(x$just, : semi-transparency is not supported on this device:
reported only once per page
ggsave("../output/progress_combi_alt_english.png", width = 9, height = 11)
Warning: Removed 1 rows containing missing values (geom_text).
Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Warning: Removed 1 rows containing missing values (geom_text).

Session info

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=nl_NL.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=nl_NL.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=nl_NL.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] lmerTest_3.1-0    lme4_1.1-21       Matrix_1.2-18     cowplot_0.9.4    
[5] ggplot2_3.3.2     DBI_1.1.0         data.table_1.13.6

loaded via a namespace (and not attached):
 [1] tidyselect_1.1.1    xfun_0.21           purrr_0.3.2        
 [4] splines_3.6.3       lattice_0.20-41     colorspace_1.4-1   
 [7] vctrs_0.3.8         htmltools_0.3.6     viridisLite_0.3.0  
[10] yaml_2.2.0          utf8_1.1.4          blob_1.2.1         
[13] rlang_0.4.10        pillar_1.4.2        nloptr_1.2.1       
[16] glue_1.3.1          withr_2.3.0         bit64_0.9-7        
[19] plyr_1.8.4          lifecycle_0.1.0     stringr_1.4.0      
[22] munsell_0.5.0       gtable_0.3.0        rvest_0.3.4        
[25] kableExtra_1.3.1    evaluate_0.14       memoise_1.1.0      
[28] labeling_0.3        knitr_1.23          fansi_0.4.0        
[31] Rcpp_1.0.6          scales_1.0.0        jsonlite_1.6       
[34] webshot_0.5.2       bit_1.1-14          digest_0.6.19      
[37] stringi_1.4.3       dplyr_0.8.3         numDeriv_2016.8-1.1
[40] cli_2.2.0           tools_3.6.3         magrittr_1.5       
[43] tibble_2.1.3        RSQLite_2.2.0       crayon_1.3.4       
[46] tidyr_1.0.0         pkgconfig_2.0.2     MASS_7.3-51.4      
[49] ellipsis_0.3.0      xml2_1.2.0          ggridges_0.5.1     
[52] httr_1.4.0          rstudioapi_0.13     assertthat_0.2.1   
[55] minqa_1.2.4         rmarkdown_2.6       R6_2.4.0           
[58] boot_1.3-25         nlme_3.1-149        compiler_3.6.3     
LS0tCnRpdGxlOiAnU2xpbVN0YW1wZW4gUGVyZm9ybWFuY2UgRHVyaW5nIExvY2tkb3duJwphdXRob3I6ICJNYWFydGVuIHZhbiBkZXIgVmVsZGUiCmRhdGU6ICJMYXN0IHVwZGF0ZWQ6IGByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgZ2l0aHViX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICBodG1sX25vdGVib29rOgogICAgc21hcnQ6IG5vCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgojIFNldHVwCgpgYGB7cn0KbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KERCSSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShsbWU0KQpsaWJyYXJ5KGxtZXJUZXN0KQoKU3lzLnNldGxvY2FsZSgiTENfVElNRSIsICJlbl9VUy5VVEYtOCIpICMgUHJpbnQgRW5nbGlzaCBkYXRlIGZvcm1hdAojIFN5cy5zZXRsb2NhbGUoIkxDX1RJTUUiLCAibmxfTkwuVVRGLTgiKSAjIFByaW50IER1dGNoIGRhdGUgZm9ybWF0CgpudW1iZXJfZm9ybWF0IDwtIHNjYWxlczo6bnVtYmVyX2Zvcm1hdChiaWcubWFyayA9ICIsIiwgZGVjaW1hbC5tYXJrID0gIi4iKSAjIFByaW50IEVuZ2xpc2ggbnVtYmVyIGZvcm1hdAojIG51bWJlcl9mb3JtYXQgPC0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KGJpZy5tYXJrID0gIi4iLCBkZWNpbWFsLm1hcmsgPSAiLCIpICMgUHJpbnQgRHV0Y2ggbnVtYmVyIGZvcm1hdAoKdGhlbWVfcGFwZXIgPC0gdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxMikgKyAKICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3VyID0gImJsYWNrIiksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJncmV5OTIiKSkKYGBgCgpTY2hvb2wgY2xvc3VyZSBhbmQgb3BlbmluZyBkYXRlcwoKU291cmNlczoKCiAgLSBodHRwczovL3d3dy5yaWprc292ZXJoZWlkLm5sL2FjdHVlZWwvbmlldXdzLzIwMjAvMDMvMTUvYWFudnVsbGVuZGUtbWFhdHJlZ2VsZW4tb25kZXJ3aWpzLWhvcmVjYS1zcG9ydAogIC0gaHR0cHM6Ly93d3cucmlqa3NvdmVyaGVpZC5ubC9hY3R1ZWVsL25pZXV3cy8yMDIwLzA1LzE5L29uZGVyd2lqcy1nYWF0LXN0YXAtdm9vci1zdGFwLW9wZW4KCmBgYHtyfQpkYXRlX3NjaG9vbHNfY2xvc2VkIDwtIGFzLlBPU0lYY3QoIjIwMjAtMDMtMTYiKQpkYXRlX3NjaG9vbHNfb3BlbmVkIDwtIGFzLlBPU0lYY3QoIjIwMjAtMDYtMDIiKQpgYGAKCgpIYW5kbGUgZGF0YWJhc2UgY29ubmVjdGlvbnMKYGBge3J9CmRiX2Nvbm5lY3QgPC0gZnVuY3Rpb24oKSB7CiAgZGIgPC0gZGJDb25uZWN0KFJTUUxpdGU6OlNRTGl0ZSgpLCBmaWxlLnBhdGgoIi4uIiwgImRhdGEiLCAibm9vcmRob2ZmLnNxbGl0ZSIpKQogIHJldHVybihkYikKfQoKZGJfZGlzY29ubmVjdCA8LSBmdW5jdGlvbihkYikgewogIGRiRGlzY29ubmVjdChkYikKfQpgYGAKCgojIERhdGEKClRoZSBkYXRhYmFzZSBjb250YWlucyBhbGwgU2xpbVN0YW1wZW4gZGF0YSBjb2xsZWN0ZWQgdmlhIE5vb3JkaG9mZidzIHBsYXRmb3JtIGluIHRocmVlIGNvdXJzZXM6ICpTdGVwcGluZyBTdG9uZXMqIChFbmdsaXNoKSwgKkdyYW5kZXMgTGlnbmVzKiAoRnJlbmNoKSwgYW5kICpOZXVlIEtvbnRha3RlKiAoR2VybWFuKS4KClRyaWFsLWxldmVsIHJlc3BvbnNlIGRhdGEgYXJlIHN0b3JlZCBpbiB0aGUgYHJlc3BvbnNlc2AgdGFibGUuCkJvb2sgaW5mb3JtYXRpb24sIHN1Y2ggYXMgdGhlIGNvdXJzZSB5ZWFyLCBib29rIHRpdGxlLCBhbmQgY2hhcHRlciwgYXJlIHN0b3JlZCBpbiB0aGUgYGJvb2tfaW5mb2AgdGFibGUuCgojIyBgcmVzcG9uc2VzYAoKfCBDb2x1bW4gICAgICAgICAgICAgICB8IFR5cGUgICAgICB8IEV4cGxhbmF0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8IGRhdGUgICAgICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgVU5JWCB0aW1lIHN0YW1wIFtzXSAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCB1c2VyX2lkICAgICAgICAgICAgICB8IGNociAgICAgICB8IHVuaXF1ZSB1c2VyIGlkZW50aWZpZXIgICAgICAgICAgICAgICAgICAgICAgICB8CnwgbWV0aG9kICAgICAgICAgICAgICAgfCBjaHIgICAgICAgfCBjb3Vyc2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IHN0YXJ0X3RpbWUgICAgICAgICAgIHwgaW50ICAgICAgIHwgZWxhcHNlZCB0aW1lIHNpbmNlIHNlc3Npb24gc3RhcnQgW21zXSAgICAgICAgIHwKfCBydCAgICAgICAgICAgICAgICAgICB8IGludCAgICAgICB8IHJlc3BvbnNlIHRpbWUgW21zXSAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgZHVyYXRpb24gICAgICAgICAgICAgfCBpbnQgICAgICAgfCB0cmlhbCBkdXJhdGlvbiBbbXNdICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGZhY3RfaWQgICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgdW5pcXVlIGZhY3QgaWRlbnRpZmllciAod2l0aGluIGNoYXB0ZXIpICAgICAgIHwKfCBjb3JyZWN0ICAgICAgICAgICAgICB8IGludCAgICAgICB8IHJlc3BvbnNlIGFjY3VyYWN5ICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgYW5zd2VyICAgICAgICAgICAgICAgfCBjaHIgICAgICAgfCB1c2VyJ3MgcmVzcG9uc2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8IGNob2ljZXMgICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgbnVtYmVyIG9mIGFuc3dlciBjaG9pY2VzICgxID09IG9wZW4gcmVzcG9uc2UpIHwKfCBiYWNrc3BhY2VfdXNlZCAgICAgICB8IGRibCAgICAgICB8IHVzZXIgcHJlc3NlZCBiYWNrc3BhY2UgZHVyaW5nIHRyaWFsICAgICAgICAgICB8CnwgYmFja3NwYWNlX3VzZWRfZmlyc3QgfCBkYmwgICAgICAgfCB1c2VyIGVyYXNlZCBmaXJzdCBjaGFyYWN0ZXIgb2YgcmVzcG9uc2UgICAgICAgfAp8IHN0dWR5ICAgICAgICAgICAgICAgIHwgaW50ICAgICAgIHwgdHJpYWwgd2FzIGEgc3R1ZHkgdHJpYWwgICAgICAgICAgICAgICAgICAgICAgIHwKfCBhbnN3ZXJfbGFuZ3VhZ2UgICAgICB8IGNociAgICAgICB8IGxhbmd1YWdlIG9mIHRoZSBhbnN3ZXIgICAgICAgICAgICAgICAgICAgICAgICB8Cnwgc3Vic2Vzc2lvbiAgICAgICAgICAgfCBpbnQgICAgICAgfCBpZGVudGlmaWVzIHBhcnQgd2l0aGluIGxlYXJuaW5nIHNlc3Npb24gICAgICAgfAp8IGJvb2tfaW5mb19pZCAgICAgICAgIHwgY2hyICAgICAgIHwgdW5pcXVlIGlkZW50aWZpZXIgb2YgYm9vayBpbmZvcm1hdGlvbiAgICAgICAgIHwKCgojIyBgYm9va19pbmZvYAoKfCBDb2x1bW4gICAgICAgICAgICAgICB8IFR5cGUgICAgICB8IEV4cGxhbmF0aW9uICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8IGJvb2tfaW5mb19pZCAgICAgICAgIHwgY2hyICAgICAgIHwgdW5pcXVlIGlkZW50aWZpZXIgb2YgYm9vayBpbmZvcm1hdGlvbiAgICAgICAgIHwKfCBtZXRob2RfZ3JvdXAgICAgICAgICB8IGNociAgICAgICB8IHllYXIgYW5kIGVkaXRpb24gICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8CnwgYm9va190aXRsZSAgICAgICAgICAgfCBjaHIgICAgICAgfCBib29rIHRpdGxlIChpbmNsLiB5ZWFyLCBsZXZlbCwgZWRpdGlvbikgICAgICAgfAp8IGJvb2tfdHlwZSAgICAgICAgICAgIHwgY2hyICAgICAgIHwgdHlwZSBvZiBib29rICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCBjaGFwdGVyICAgICAgICAgICAgICB8IGNociAgICAgICB8IGNoYXB0ZXIgbnVtYmVyIGFuZCB0aXRsZSAgICAgICAgICAgICAgICAgICAgICB8CgoKUHJldmlldyBmaXJzdCAxMCByb3dzCmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKcmVzcG9uc2VzX3RvcCA8LSBkYkdldFF1ZXJ5KGRiLCAiU0VMRUNUICogRlJPTSByZXNwb25zZXNfbm9kdXBsaWNhdGVzIExJTUlUIDEwIikKcmVzcG9uc2VzX3RvcAoKYm9va19pbmZvX3RvcCA8LSBkYkdldFF1ZXJ5KGRiLCAiU0VMRUNUICogRlJPTSBib29rX2luZm8gTElNSVQgMTAiKQpib29rX2luZm9fdG9wCmRiX2Rpc2Nvbm5lY3QoZGIpCmBgYAoKCiMgUGVyZm9ybWFuY2UKClRoZXJlIGFyZSBzZXZlcmFsIG1lYXN1cmVzIG9mIGxlYXJuaW5nIHBlcmZvcm1hbmNlIHdlIGNhbiBsb29rIGF0LgpUaGUgbW9zdCBzdHJhaWdodC1mb3J3YXJkIG9mIHRoZXNlIGFyZSByZXNwb25zZSBhY2N1cmFjeSBhbmQgcmVzcG9uc2UgdGltZS4KCkltcG9ydGFudCBmYWN0b3JzIHRvIGtlZXAgaW4gbWluZDogcXVlc3Rpb24gdHlwZSAobXVsdGlwbGUgY2hvaWNlIG9yIG9wZW4gYW5zd2VyKSBhbmQgbGFuZ3VhZ2UuCk5vdGUgdGhhdCB3ZSBjYW5ub3QgZGlzdGluZ3Vpc2ggYmV0d2VlbiBOTC1YIGFuZCBYLVgsIHNpbmNlIHdlIG9ubHkga25vdyB0aGUgbGFuZ3VhZ2Ugb2YgdGhlIGFuc3dlci4KCiMjIFJlc3BvbnNlIGFjY3VyYWN5CgojIyMgV2hvbGUgcG9wdWxhdGlvbgoKYGBge3J9CmRiIDwtIGRiX2Nvbm5lY3QoKQpjb3JyZWN0IDwtIGRiR2V0UXVlcnkoZGIsIAogICAgICAgICAgICAgICAgICAgICAgIlNFTEVDVCByLm1ldGhvZCBBUyAnbWV0aG9kJywKICAgICAgICAgICAgICAgICAgICAgIERBVEUoci5kYXRlICsgMzYwMCwgJ3VuaXhlcG9jaCcpIEFTICdkb3knLAogICAgICAgICAgICAgICAgICAgICAgci51c2VyX2lkIEFTICd1c2VyJywKICAgICAgICAgICAgICAgICAgICAgIHIuY2hvaWNlcyA+IDEgQVMgJ21jcScsCiAgICAgICAgICAgICAgICAgICAgICByLmNvcnJlY3QgQVMgJ2NvcnJlY3QnLAogICAgICAgICAgICAgICAgICAgICAgQ09VTlQoKikgQVMgJ24nCiAgICAgICAgICAgICAgICAgICAgICBGUk9NICdyZXNwb25zZXNfbm9kdXBsaWNhdGVzJyByCiAgICAgICAgICAgICAgICAgICAgICBXSEVSRSByLnN0dWR5ID09IDAKICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHIubWV0aG9kLAogICAgICAgICAgICAgICAgICAgICAgREFURShyLmRhdGUgKyAzNjAwLCAndW5peGVwb2NoJyksCiAgICAgICAgICAgICAgICAgICAgICByLnVzZXJfaWQsCiAgICAgICAgICAgICAgICAgICAgICByLmNob2ljZXMgPiAxLAogICAgICAgICAgICAgICAgICAgICAgci5jb3JyZWN0IgopCnNldERUKGNvcnJlY3QpCmRiX2Rpc2Nvbm5lY3QoZGIpCmBgYAoKRmlsbCBpbiBtaXNzaW5nIHJvd3MgKHdoZXJlIGFsbCB0cmlhbHMgb24gYSBkYXkgd2VyZSBjb3JyZWN0L2luY29ycmVjdCk6CmBgYHtyfQpjb3JyZWN0IDwtIHRpZHlyOjpjb21wbGV0ZShjb3JyZWN0LCB0aWR5cjo6bmVzdGluZyhtZXRob2QsIGRveSwgdXNlciwgbWNxKSwgY29ycmVjdCwgZmlsbCA9IGxpc3QobiA9IDApKQpzZXREVChjb3JyZWN0KQpgYGAKCmBgYHtyfQpjb3JyZWN0WywgbWNxIDo9IGFzLmxvZ2ljYWwobWNxKV0KYGBgCgpgYGB7cn0KYWNjdXJhY3kgPC0gY29ycmVjdFssIC4oYWNjdXJhY3kgPSBuW2NvcnJlY3QgPT0gMV0vc3VtKG4pLCBuID0gc3VtKG4pKSwgYnkgPSAuKG1ldGhvZCwgZG95LCB1c2VyLCBtY3EpXQpgYGAKCkFkZCBhIHNjaG9vbCB5ZWFyIGNvbHVtbiAoY3V0b2ZmIGRhdGU6IDEgQXVndXN0KToKYGBge3J9CmFjY3VyYWN5WywgZG95X3Bvc2l4IDo9IGFzLlBPU0lYY3QoZG95KV0KYWNjdXJhY3lbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KYGBgCgpBZGQgc2Vuc2libGUgY291cnNlIG5hbWVzOgpgYGB7cn0KYWNjdXJhY3lbLCBjb3Vyc2UgOj0gaWZlbHNlKG1ldGhvZCA9PSAiR3JhbmRlcyBMaWduZXMiLCAiRnJlbmNoIiwgaWZlbHNlKG1ldGhvZCA9PSAiU3RlcHBpbmcgU3RvbmVzIiwgIkVuZ2xpc2giLCAiR2VybWFuIikpXQpgYGAKCkFsaWduIHNjaG9vbCB5ZWFyczoKYGBge3J9CmFjY3VyYWN5W3NjaG9vbF95ZWFyID09ICIxOC8xOSIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGFzLlBPU0lYY3QoZG95X3Bvc2l4ICsgMzY1KjI0KjYwKjYwLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpXQphY2N1cmFjeVtzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBkb3lfcG9zaXhdCmBgYAoKVXNlIGN1dC5EYXRlKCkgdG8gYmluIGRhdGVzIGJ5IHdlZWsuIEVhY2ggZGF5IGlzIGFzc2lnbmVkIHRoZSBkYXRlIG9mIHRoZSBtb3N0IHJlY2VudCBNb25kYXkuCmBgYHtyfQphY2N1cmFjeVssIGRveV9wb3NpeF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4LCAid2VlayIpXQphY2N1cmFjeVssIGRveV9wb3NpeF9hbGlnbmVkX3dlZWsgOj0gY3V0LlBPU0lYdChkb3lfcG9zaXhfYWxpZ25lZCwgIndlZWsiKV0KYGBgCgpgYGB7cn0KYWNjdXJhY3lfYnlfd2Vla19hbmRfdXNlciA8LSBhY2N1cmFjeVssIC4oYWNjdXJhY3kgPSBzdW0oYWNjdXJhY3kqbikvc3VtKG4pKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIsIGRveV9wb3NpeF9hbGlnbmVkX3dlZWssIHVzZXIsIG1jcSldCmFjY3VyYWN5X2J5X3dlZWsgPC0gYWNjdXJhY3lfYnlfd2Vla19hbmRfdXNlclssIC4oYWNjdXJhY3lfbWVhbiA9IG1lYW4oYWNjdXJhY3ksIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY3VyYWN5X3NlID0gc2QoYWNjdXJhY3ksIG5hLnJtID0gVFJVRSkvc3FydCguTiksIG4gPSAuTiksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBtY3EpXQpgYGAKCgpBZGQgcXVlc3Rpb24gdHlwZSBsYWJlbHM6CmBgYHtyfQphY2N1cmFjeV9ieV93ZWVrWywgcXVlc3Rpb25fdHlwZSA6PSBpZmVsc2UobWNxID09IFRSVUUsICJNdWx0aXBsZVxuY2hvaWNlIiwgIk9wZW5cbmFuc3dlciIpXQpgYGAKCgpQbG90IHJlc3BvbnNlIGFjY3VyYWN5IGJ5IHdlZWsgKG1lYW4gKy8tIDEgU0UpLgpgYGB7cn0KcF9hY2MgPC0gZ2dwbG90KGFjY3VyYWN5X2J5X3dlZWtbKGNvdXJzZSA9PSAiRW5nbGlzaCIgJiBtY3EgPT0gVFJVRSkgfCBjb3Vyc2UgPT0gIkZyZW5jaCIsXSwKICAgICAgICAgICAgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gYWNjdXJhY3lfbWVhbiwgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZCguIH4gY291cnNlKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IDAsIHltYXggPSAxLjA1LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyLCBhbHBoYSA9IC45KSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBhY2N1cmFjeV9tZWFuIC0gYWNjdXJhY3lfc2UsIHltYXggPSBhY2N1cmFjeV9tZWFuICsgYWNjdXJhY3lfc2UsIGNvbG91ciA9IE5VTEwpLCBhbHBoYSA9IDAuMikgKwogIGdlb21fbGluZShhZXMobHR5ID0gcXVlc3Rpb25fdHlwZSkpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYyguNywgMSksIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJBY2N1cmFjeSIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBsdHkgPSAiUXVlc3Rpb24gdHlwZSIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgbHR5ID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMikpICsKICB0aGVtZV9wYXBlcgpwX2FjYwpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZS5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L2FjY19ieV9xdWVzdGlvbl90eXBlLmVwcyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKZ2dzYXZlKCIuLi9vdXRwdXQvYWNjX2J5X3F1ZXN0aW9uX3R5cGUucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzKQpgYGAKCgojIyMgQnkgbGV2ZWwgYW5kIHllYXIKCmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKY29ycmVjdF9zdHJhdCA8LSBkYkdldFF1ZXJ5KGRiLCAKICAgICAgICAgICAgICAgICAgICAgICJTRUxFQ1Qgci5tZXRob2QgQVMgJ21ldGhvZCcsCiAgICAgICAgICAgICAgICAgICAgICByLmJvb2tfaW5mb19pZCBBUyAnYm9va19pbmZvX2lkJywKICAgICAgICAgICAgICAgICAgICAgIERBVEUoci5kYXRlICsgMzYwMCwgJ3VuaXhlcG9jaCcpIEFTICdkb3knLAogICAgICAgICAgICAgICAgICAgICAgci51c2VyX2lkIEFTICd1c2VyJywKICAgICAgICAgICAgICAgICAgICAgIHIuY2hvaWNlcyA+IDEgQVMgJ21jcScsCiAgICAgICAgICAgICAgICAgICAgICByLmNvcnJlY3QgQVMgJ2NvcnJlY3QnLAogICAgICAgICAgICAgICAgICAgICAgQ09VTlQoKikgQVMgJ24nCiAgICAgICAgICAgICAgICAgICAgICBGUk9NICdyZXNwb25zZXNfbm9kdXBsaWNhdGVzJyByCiAgICAgICAgICAgICAgICAgICAgICBXSEVSRSByLnN0dWR5ID09IDAKICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIHIubWV0aG9kLAogICAgICAgICAgICAgICAgICAgICAgci5ib29rX2luZm9faWQsCiAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSwKICAgICAgICAgICAgICAgICAgICAgIHIudXNlcl9pZCwKICAgICAgICAgICAgICAgICAgICAgIHIuY2hvaWNlcyA+IDEsCiAgICAgICAgICAgICAgICAgICAgICByLmNvcnJlY3QiCikKc2V0RFQoY29ycmVjdF9zdHJhdCkKZGJfZGlzY29ubmVjdChkYikKYGBgCgpGaWxsIGluIG1pc3Npbmcgcm93cyAod2hlcmUgYWxsIHRyaWFscyBvbiBhIGRheSB3ZXJlIGNvcnJlY3QvaW5jb3JyZWN0KToKYGBge3J9CmNvcnJlY3Rfc3RyYXQgPC0gdGlkeXI6OmNvbXBsZXRlKGNvcnJlY3Rfc3RyYXQsIHRpZHlyOjpuZXN0aW5nKG1ldGhvZCwgYm9va19pbmZvX2lkLCBkb3ksIHVzZXIsIG1jcSksIGNvcnJlY3QsIGZpbGwgPSBsaXN0KG4gPSAwKSkKc2V0RFQoY29ycmVjdF9zdHJhdCkKYGBgCgpgYGB7cn0KY29ycmVjdF9zdHJhdFssIG1jcSA6PSBhcy5sb2dpY2FsKG1jcSldCmBgYAoKQWRkIGJvb2sgaW5mb3JtYXRpb246CmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKYm9va19pbmZvIDwtIGRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgRElTVElOQ1QgKiBGUk9NICdib29rX2luZm8nIikKZGJfZGlzY29ubmVjdChkYikKCnNldERUKGJvb2tfaW5mbykKYGBgCgpgYGB7cn0KY29ycmVjdF9zdHJhdFtib29rX2luZm9bYm9va190eXBlID09ICJIb29mZGJvZWsiLF0sIG9uICA9ICJib29rX2luZm9faWQiLCBjKCJtZXRob2RfZ3JvdXAiLCAiYm9va190aXRsZSIpIDo9IC4oaS5tZXRob2RfZ3JvdXAsIGkuYm9va190aXRsZSldCmBgYAoKQWRkIHNlbnNpYmxlIGNvdXJzZSBuYW1lczoKYGBge3J9CmNvcnJlY3Rfc3RyYXRbLCBjb3Vyc2UgOj0gaWZlbHNlKG1ldGhvZCA9PSAiR3JhbmRlcyBMaWduZXMiLCAiRnJlbmNoIiwgaWZlbHNlKG1ldGhvZCA9PSAiU3RlcHBpbmcgU3RvbmVzIiwgIkVuZ2xpc2giLCAiR2VybWFuIikpXQpgYGAKCkFkZCBhIHNjaG9vbCB5ZWFyIGNvbHVtbiAoY3V0b2ZmIGRhdGU6IDEgQXVndXN0KToKYGBge3J9CmNvcnJlY3Rfc3RyYXRbLCBkb3lfcG9zaXggOj0gYXMuUE9TSVhjdChkb3kpXQpjb3JyZWN0X3N0cmF0Wywgc2Nob29sX3llYXIgOj0gaWZlbHNlKGRveV9wb3NpeCA8ICIyMDE5LTA4LTAxIiwgIjE4LzE5IiwgIjE5LzIwIildCmBgYAoKU2ltcGxpZnkgbGV2ZWwgbmFtZXM6CmBgYHtyfQojIEtlZXAgYWxsIGRpc3RpbmN0aW9ucwpjb3JyZWN0X3N0cmF0WywgYm9va190aXRsZV9zaW1wbGUgOj0gc3RyaW5ncjo6c3RyX3N1Yihib29rX3RpdGxlLCAzLCAtMTApXQpjb3JyZWN0X3N0cmF0WywgYm9va190aXRsZV9zaW1wbGUgOj0gZmFjdG9yKGJvb2tfdGl0bGVfc2ltcGxlLCBsZXZlbHMgPSBjKCJ2bWJvIGIvbHdvbyIsICJ2bWJvIGIiLCAidm1ibyBiayIsICJ2bWJvIGsiLCAidm1ibyBrZ3QiLCAidm1iby1ndCIsICJ2bWJvIGd0IiwgInZtYm8tZ3QvaGF2byIsICJ2bWJvICh0KWh2IiwgImhhdm8iLCAiaGF2byB2d28iLCAidndvIikpXQoKIyBTaW1wbGlmeSB0byB0aHJlZSBsZXZlbHMKY29ycmVjdF9zdHJhdFssIGxldmVsIDo9IGRwbHlyOjpjYXNlX3doZW4oCiAgZ3JlcGwoICJodiIsIGJvb2tfdGl0bGUpIH4gIkdlbmVyYWwgc2Vjb25kYXJ5XG4oaGF2bykiLAogIGdyZXBsKCJ2bWJvIiwgYm9va190aXRsZSkgfiAiUHJlLXZvY2F0aW9uYWxcbih2bWJvKSIsCiAgZ3JlcGwoImhhdm8iLCBib29rX3RpdGxlKSB+ICJHZW5lcmFsIHNlY29uZGFyeVxuKGhhdm8pIiwKICBncmVwbCgidndvIiwgYm9va190aXRsZSkgfiAiUHJlLXVuaXZlcnNpdHlcbih2d28pIiwKICBUUlVFIH4gIk90aGVyIildCmNvcnJlY3Rfc3RyYXRbLCBsZXZlbCA6PSBmYWN0b3IobGV2ZWwsIGxldmVscyA9IGMoIk90aGVyIiwgIlByZS12b2NhdGlvbmFsXG4odm1ibykiLCAiR2VuZXJhbCBzZWNvbmRhcnlcbihoYXZvKSIsICJQcmUtdW5pdmVyc2l0eVxuKHZ3bykiKSldCmBgYAoKU2ltcGxpZnkgeWVhciBuYW1lczoKYGBge3J9CmNvcnJlY3Rfc3RyYXRbLCB5ZWFyIDo9IGRwbHlyOjpjYXNlX3doZW4oCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAxICg1ZSBFZC4pIiB+ICJZZWFyIDEiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMiAoNWUgRWQuKSIgfiAiWWVhciAyIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDMgKDVlIEVkLikiIH4gIlllYXIgMyIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAzLzQgKDVlIEVkLikiIH4gIlllYXIgMy80IiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDQgKDVlIEVkLikiIH4gIlllYXIgNCIsCiAgbWV0aG9kX2dyb3VwID09ICJUd2VlZGUgRmFzZSAoNmUgRWQuKSIgfiAiVHdlZWRlIEZhc2UiLAogIFRSVUUgfiAiT3RoZXIiKV0KYGBgCgpDb25zb2xpZGF0ZSBieSBkYXk6CmBgYHtyfQphY2N1cmFjeV9zdHJhdCA8LSBjb3JyZWN0X3N0cmF0WywgLihhY2N1cmFjeSA9IG5bY29ycmVjdCA9PSAxXS9zdW0obiksIG4gPSBzdW0obikpLCBieSA9IC4oc2Nob29sX3llYXIsIGRveV9wb3NpeCwgY291cnNlLCBsZXZlbCwgeWVhciwgdXNlciwgbWNxKV0KYGBgCgpBbGlnbiBzY2hvb2wgeWVhcnM6CmBgYHtyfQphY2N1cmFjeV9zdHJhdFtzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KYWNjdXJhY3lfc3RyYXRbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKClVzZSBjdXQuRGF0ZSgpIHRvIGJpbiBkYXRlcyBieSB3ZWVrLiBFYWNoIGRheSBpcyBhc3NpZ25lZCB0aGUgZGF0ZSBvZiB0aGUgbW9zdCByZWNlbnQgTW9uZGF5LgpgYGB7cn0KYWNjdXJhY3lfc3RyYXRbLCBkb3lfcG9zaXhfd2VlayA6PSBjdXQuUE9TSVh0KGRveV9wb3NpeCwgIndlZWsiKV0KYWNjdXJhY3lfc3RyYXRbLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJ3ZWVrIildCmBgYAoKYGBge3J9CmFjY3VyYWN5X3N0cmF0X2J5X3dlZWtfYW5kX3VzZXIgPC0gYWNjdXJhY3lfc3RyYXRbLCAuKGFjY3VyYWN5ID0gc3VtKGFjY3VyYWN5Km4pL3N1bShuKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBsZXZlbCwgeWVhciwgdXNlciwgbWNxKV0KYWNjdXJhY3lfc3RyYXRfYnlfd2VlayA8LSBhY2N1cmFjeV9zdHJhdF9ieV93ZWVrX2FuZF91c2VyWywgLihhY2N1cmFjeV9tZWFuID0gbWVhbihhY2N1cmFjeSwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWNjdXJhY3lfc2UgPSBzZChhY2N1cmFjeSwgbmEucm0gPSBUUlVFKS9zcXJ0KC5OKSwgbiA9IC5OKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIsIGRveV9wb3NpeF9hbGlnbmVkX3dlZWssIGxldmVsLCB5ZWFyLCBtY3EpXQpgYGAKCkFkZCBxdWVzdGlvbiB0eXBlIGxhYmVsczoKYGBge3J9CmFjY3VyYWN5X3N0cmF0X2J5X3dlZWtfYW5kX3VzZXJbLCBxdWVzdGlvbl90eXBlIDo9IGlmZWxzZShtY3EgPT0gVFJVRSwgIk11bHRpcGxlXG5jaG9pY2UiLCAiT3BlblxuYW5zd2VyIildCmFjY3VyYWN5X3N0cmF0X2J5X3dlZWtbLCBxdWVzdGlvbl90eXBlIDo9IGlmZWxzZShtY3EgPT0gVFJVRSwgIk11bHRpcGxlXG5jaG9pY2UiLCAiT3BlblxuYW5zd2VyIildCmBgYAoKSG93IG1hbnkgdW5pcXVlIHVzZXJzIHBlciBncm91cD8KYGBge3J9CmFjY3VyYWN5X3N0cmF0X2J5X3dlZWtfYW5kX3VzZXJbLCAuKHVuaXF1ZV91c2VycyA9IGxlbmd0aCh1bmlxdWUodXNlcikpKSwgIGJ5ID0gLihjb3Vyc2UsIGxldmVsLCB5ZWFyLCBzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSldCmBgYAoKClBsb3QgcmVzcG9uc2UgYWNjdXJhY3kgYnkgd2VlayAobWVhbiArLy0gMSBTRSkuCmBgYHtyfQpwX2FjY19sZXZlbF95ZWFyIDwtIGdncGxvdChhY2N1cmFjeV9zdHJhdF9ieV93ZWVrW2NvdXJzZSA9PSAiRnJlbmNoIiAmIGxldmVsICE9ICJPdGhlciIsXSwKICAgICAgICAgICAgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gYWNjdXJhY3lfbWVhbiwgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IHllYXIpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gMCwgeW1heCA9IDEuMDUsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIsIGFscGhhID0gLjkpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IGFjY3VyYWN5X21lYW4gLSBhY2N1cmFjeV9zZSwgeW1heCA9IGFjY3VyYWN5X21lYW4gKyBhY2N1cmFjeV9zZSwgY29sb3VyID0gTlVMTCksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9saW5lKGFlcyhsdHkgPSBxdWVzdGlvbl90eXBlKSkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLjQsIDEpKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkFjY3VyYWN5IiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGx0eSA9ICJRdWVzdGlvbiB0eXBlIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgZmlsbCA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBsdHkgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAyKSkgKwogIHRoZW1lX3BhcGVyCgpwX2FjY19sZXZlbF95ZWFyCgpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV9mcmVuY2hfbGV2ZWxfeWVhci5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L2FjY19ieV9xdWVzdGlvbl90eXBlX2ZyZW5jaF9sZXZlbF95ZWFyLmVwcyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKZ2dzYXZlKCIuLi9vdXRwdXQvYWNjX2J5X3F1ZXN0aW9uX3R5cGVfZnJlbmNoX2xldmVsX3llYXIucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpgYGAKCmBgYHtyfQpwX2FjY19sZXZlbF95ZWFyIDwtIGdncGxvdChhY2N1cmFjeV9zdHJhdF9ieV93ZWVrW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiBsZXZlbCAhPSAiT3RoZXIiICYgcXVlc3Rpb25fdHlwZSA9PSAiTXVsdGlwbGVcbmNob2ljZSIsXSwKICAgICAgICAgICAgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gYWNjdXJhY3lfbWVhbiwgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IHllYXIpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gMCwgeW1heCA9IDEuMDUsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIsIGFscGhhID0gLjkpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IGFjY3VyYWN5X21lYW4gLSBhY2N1cmFjeV9zZSwgeW1heCA9IGFjY3VyYWN5X21lYW4gKyBhY2N1cmFjeV9zZSwgY29sb3VyID0gTlVMTCksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLjQsIDEpKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkFjY3VyYWN5IiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGx0eSA9ICJRdWVzdGlvbiB0eXBlIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgZmlsbCA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBsdHkgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAyKSkgKwogIHRoZW1lX3BhcGVyCgpwX2FjY19sZXZlbF95ZWFyCgpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV9lbmdsaXNoX2xldmVsX3llYXIucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV9lbmdsaXNoX2xldmVsX3llYXIuZXBzIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV9lbmdsaXNoX2xldmVsX3llYXIucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpgYGAKCiMjIyBCeSBsZXZlbAoKYGBge3J9CmFjY3VyYWN5X2xldmVsX2J5X3dlZWtfYW5kX3VzZXIgPC0gYWNjdXJhY3lfc3RyYXRbLCAuKGFjY3VyYWN5ID0gc3VtKGFjY3VyYWN5Km4pL3N1bShuKSksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBsZXZlbCwgdXNlciwgbWNxKV0KCmFjY3VyYWN5X2xldmVsX2J5X3dlZWsgPC0gYWNjdXJhY3lfbGV2ZWxfYnlfd2Vla19hbmRfdXNlclssIC4oYWNjdXJhY3lfbWVhbiA9IG1lYW4oYWNjdXJhY3ksIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY3VyYWN5X3NlID0gc2QoYWNjdXJhY3ksIG5hLnJtID0gVFJVRSkvc3FydCguTiksIG4gPSAuTiksIGJ5ID0gLihjb3Vyc2UsIHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBsZXZlbCwgbWNxKV0KYGBgCgpBZGQgcXVlc3Rpb24gdHlwZSBsYWJlbHM6CmBgYHtyfQphY2N1cmFjeV9sZXZlbF9ieV93ZWVrWywgcXVlc3Rpb25fdHlwZSA6PSBpZmVsc2UobWNxID09IFRSVUUsICJNdWx0aXBsZVxuY2hvaWNlIiwgIk9wZW5cbmFuc3dlciIpXQpgYGAKCkhvdyBtYW55IHVzZXJzIGluIGVhY2ggZ3JvdXA/CmBgYHtyfQphY2N1cmFjeV9sZXZlbF9ieV93ZWVrX2FuZF91c2VyWywgLih1bmlxdWVfdXNlcnMgPSBsZW5ndGgodW5pcXVlKHVzZXIpKSksICBieSA9IC4oY291cnNlLCBsZXZlbCwgc2Nob29sX3llYXIsIG1jcSldCmBgYAoKClBsb3QgcmVzcG9uc2UgYWNjdXJhY3kgYnkgd2VlayAobWVhbiArLy0gMSBTRSkuCmBgYHtyfQpwX2FjY19sZXZlbCA8LSBnZ3Bsb3QoYWNjdXJhY3lfbGV2ZWxfYnlfd2Vla1soKGNvdXJzZSA9PSAiRW5nbGlzaCIgJiBxdWVzdGlvbl90eXBlID09ICJNdWx0aXBsZVxuY2hvaWNlIikgfCBjb3Vyc2UgPT0gIkZyZW5jaCIpICYgbGV2ZWwgIT0gIk90aGVyIixdLAogICAgICAgICAgICBhZXMoeCA9IGFzLlBPU0lYY3QoZG95X3Bvc2l4X2FsaWduZWRfd2VlayksIHkgPSBhY2N1cmFjeV9tZWFuLCBncm91cCA9IGludGVyYWN0aW9uKHNjaG9vbF95ZWFyLCBxdWVzdGlvbl90eXBlKSwgY29sb3VyID0gc2Nob29sX3llYXIsIGZpbGwgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKGxldmVsIH4gY291cnNlKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IDAsIHltYXggPSAxLjA1LCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyLCBhbHBoYSA9IC45KSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBhY2N1cmFjeV9tZWFuIC0gYWNjdXJhY3lfc2UsIHltYXggPSBhY2N1cmFjeV9tZWFuICsgYWNjdXJhY3lfc2UsIGNvbG91ciA9IE5VTEwpLCBhbHBoYSA9IDAuMikgKwogIGdlb21fbGluZShhZXMobHR5ID0gcXVlc3Rpb25fdHlwZSkpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKC42LCAxKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJBY2N1cmFjeSIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBsdHkgPSAiUXVlc3Rpb24gdHlwZSIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgbHR5ID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMikpICsKICB0aGVtZV9wYXBlcgoKcF9hY2NfbGV2ZWwKCmdnc2F2ZSgiLi4vb3V0cHV0L2FjY19ieV9xdWVzdGlvbl90eXBlX2xldmVsLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKZ2dzYXZlKCIuLi9vdXRwdXQvYWNjX2J5X3F1ZXN0aW9uX3R5cGVfbGV2ZWwuZXBzIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV9sZXZlbC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmBgYAoKCiMjIyBCeSB5ZWFyCgpgYGB7cn0KYWNjdXJhY3lfeWVhcl9ieV93ZWVrX2FuZF91c2VyIDwtIGFjY3VyYWN5X3N0cmF0WywgLihhY2N1cmFjeSA9IHN1bShhY2N1cmFjeSpuKS9zdW0obikpLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfd2VlaywgeWVhciwgdXNlciwgbWNxKV0KCmFjY3VyYWN5X3llYXJfYnlfd2VlayA8LSBhY2N1cmFjeV95ZWFyX2J5X3dlZWtfYW5kX3VzZXJbLCAuKGFjY3VyYWN5X21lYW4gPSBtZWFuKGFjY3VyYWN5LCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY2N1cmFjeV9zZSA9IHNkKGFjY3VyYWN5LCBuYS5ybSA9IFRSVUUpL3NxcnQoLk4pLCBuID0gLk4pLCBieSA9IC4oY291cnNlLCBzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfd2VlaywgeWVhciwgbWNxKV0KYGBgCgpBZGQgcXVlc3Rpb24gdHlwZSBsYWJlbHM6CmBgYHtyfQphY2N1cmFjeV95ZWFyX2J5X3dlZWtbLCBxdWVzdGlvbl90eXBlIDo9IGlmZWxzZShtY3EgPT0gVFJVRSwgIk11bHRpcGxlXG5jaG9pY2UiLCAiT3BlblxuYW5zd2VyIildCmBgYAoKSG93IG1hbnkgdXNlcnMgaW4gZWFjaCBncm91cD8KYGBge3J9CmFjY3VyYWN5X3llYXJfYnlfd2Vla19hbmRfdXNlclssIC4odW5pcXVlX3VzZXJzID0gbGVuZ3RoKHVuaXF1ZSh1c2VyKSkpLCAgYnkgPSAuKGNvdXJzZSwgeWVhciwgc2Nob29sX3llYXIsIG1jcSldCmBgYAoKClBsb3QgcmVzcG9uc2UgYWNjdXJhY3kgYnkgd2VlayAobWVhbiArLy0gMSBTRSkuCmBgYHtyfQpwX2FjY195ZWFyIDwtIGdncGxvdChhY2N1cmFjeV95ZWFyX2J5X3dlZWtbKChjb3Vyc2UgPT0gIkVuZ2xpc2giICYgcXVlc3Rpb25fdHlwZSA9PSAiTXVsdGlwbGVcbmNob2ljZSIpIHwgY291cnNlID09ICJGcmVuY2giKSAmIHllYXIgIT0gIk90aGVyIixdLAogICAgICAgICAgICBhZXMoeCA9IGFzLlBPU0lYY3QoZG95X3Bvc2l4X2FsaWduZWRfd2VlayksIHkgPSBhY2N1cmFjeV9tZWFuLCBncm91cCA9IGludGVyYWN0aW9uKHNjaG9vbF95ZWFyLCBxdWVzdGlvbl90eXBlKSwgY29sb3VyID0gc2Nob29sX3llYXIsIGZpbGwgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKHllYXIgfiBjb3Vyc2UpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gMCwgeW1heCA9IDEuMDUsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIsIGFscGhhID0gLjkpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IGFjY3VyYWN5X21lYW4gLSBhY2N1cmFjeV9zZSwgeW1heCA9IGFjY3VyYWN5X21lYW4gKyBhY2N1cmFjeV9zZSwgY29sb3VyID0gTlVMTCksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9saW5lKGFlcyhsdHkgPSBxdWVzdGlvbl90eXBlKSkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLjYsIDEpKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkFjY3VyYWN5IiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGx0eSA9ICJRdWVzdGlvbiB0eXBlIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgZmlsbCA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBsdHkgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAyKSkgKwogIHRoZW1lX3BhcGVyCgpwX2FjY195ZWFyCgpnZ3NhdmUoIi4uL291dHB1dC9hY2NfYnlfcXVlc3Rpb25fdHlwZV95ZWFyLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKZ2dzYXZlKCIuLi9vdXRwdXQvYWNjX2J5X3F1ZXN0aW9uX3R5cGVfeWVhci5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L2FjY19ieV9xdWVzdGlvbl90eXBlX3llYXIucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpgYGAKCgoKIyMjIFJlZ3Jlc3Npb24gbW9kZWwKCkZpdCBhIG1peGVkIGVmZmVjdHMgbW9kZWwgdG8gdGhlIGRhaWx5IGFjY3VyYWN5IGRhdGE6CmBgYHtyfQphY2N1cmFjeVssIHBlcmlvZCA6PSBkcGx5cjo6Y2FzZV93aGVuKAogIGRveV9wb3NpeF9hbGlnbmVkID49IGRhdGVfc2Nob29sc19vcGVuZWQgfiAicG9zdC1sb2NrZG93biIsCiAgZG95X3Bvc2l4X2FsaWduZWQgPj0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCAmIGRveV9wb3NpeF9hbGlnbmVkIDwgZGF0ZV9zY2hvb2xzX29wZW5lZCB+ICJkdXJpbmctbG9ja2Rvd24iLAogIGRveV9wb3NpeF9hbGlnbmVkIDwgZGF0ZV9zY2hvb2xzX29wZW5lZCB+ICJwcmUtbG9ja2Rvd24iCildCgojIFJlb3JkZXIgZmFjdG9yIGxldmVscyBzbyB0aGF0IGludGVyY2VwdCBpcyBwcmUtbG9ja2Rvd24gb3BlbiBhbnN3ZXIgaW4gMTkvMjAKYWNjdXJhY3lbLCBwZXJpb2QgOj0gZmFjdG9yKHBlcmlvZCwgbGV2ZWxzID0gYygicHJlLWxvY2tkb3duIiwgImR1cmluZy1sb2NrZG93biIsICJwb3N0LWxvY2tkb3duIikpXQphY2N1cmFjeVssIHNjaG9vbF95ZWFyIDo9IGZhY3RvcihzY2hvb2xfeWVhciwgbGV2ZWxzID0gYygiMTkvMjAiLCAiMTgvMTkiKSldCmFjY3VyYWN5WywgbWNxIDo9IGZhY3RvcihtY3EsIGxldmVscyA9IGMoRkFMU0UsIFRSVUUpKV0KYGBgCgpTaW5jZSB3ZSBrbm93IHRoZSBudW1iZXIgb2YgdHJpYWxzIHBlciBkYXkgYW5kIHRoZSBwcm9wb3J0aW9uIGNvcnJlY3QgKGFjY3VyYWN5KSwgd2UgY2FuIHVzZSBhIGJpbm9taWFsIEdMTU06CmBgYHtyfQppZighZmlsZS5leGlzdHMoIi4uL291dHB1dC9tX2FjY19maXQucmRzIikpIHsKICBtX2FjYyA8LSBnbG1lcihhY2N1cmFjeSB+IHBlcmlvZCpzY2hvb2xfeWVhciptY3EgKyAoMSB8IHVzZXIpICsgKDEgfCBjb3Vyc2UpLAogICAgICAgICAgICAgICAgIGRhdGEgPSBhY2N1cmFjeVsoY291cnNlID09ICJFbmdsaXNoIiAmIG1jcSA9PSBUUlVFKSB8IGNvdXJzZSA9PSAiRnJlbmNoIixdLAogICAgICAgICAgICAgICAgIGZhbWlseSA9ICJiaW5vbWlhbCIsIAogICAgICAgICAgICAgICAgIHdlaWdodHMgPSBuLAogICAgICAgICAgICAgICAgIG5BR1EgPSAwLAogICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBnbG1lckNvbnRyb2wob3B0aW1pemVyID0gImJvYnlxYSIsIG9wdEN0cmwgPSBsaXN0KG1heGZ1biA9IDFlNikpKQogIHNhdmVSRFMobV9hY2MsICIuLi9vdXRwdXQvbV9hY2NfZml0LnJkcyIpCn0gZWxzZSB7CiAgbV9hY2MgPC0gcmVhZFJEUygiLi4vb3V0cHV0L21fYWNjX2ZpdC5yZHMiKQp9CgptX2FjY19zdW1tYXJ5IDwtIHN1bW1hcnkobV9hY2MpCm1fYWNjX3N1bW1hcnkKYGBgCgpTYXZlIGNvZWZmaWNpZW50cyBhcyBhIHRhYmxlIGZvciBpbiB0aGUgcGFwZXI6CmBgYHtyfQptX2FjY19jb2VmIDwtIGFzLmRhdGEuZnJhbWUobV9hY2Nfc3VtbWFyeSRjb2VmZmljaWVudHMpCnNldERUKG1fYWNjX2NvZWYsIGtlZXAucm93bmFtZXMgPSBUUlVFKQptX2FjY19jb2VmJHJuIDwtIGMoIkludGVyY2VwdCBcXHNtYWxseyhQZXJpb2Q6IHByZS1sb2NrZG93biwgU2Nob29sIHllYXI6IDE5LzIwLCBRdWVzdGlvbiB0eXBlOiBvcGVuIGFuc3dlcil9IiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IGxvY2tkb3duIiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IHBvc3QtbG9ja2Rvd24iLAogICAgICAgICAgICAgICAgICAgIlNjaG9vbCB5ZWFyOiAxOC8xOSIsCiAgICAgICAgICAgICAgICAgICAiUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IGxvY2tkb3duICRcXHRpbWVzJCBTY2hvb2wgeWVhcjogMTgvMTkiLAogICAgICAgICAgICAgICAgICAgIlBlcmlvZDogcG9zdC1sb2NrZG93biAkXFx0aW1lcyQgU2Nob29sIHllYXI6IDE4LzE5IiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IGxvY2tkb3duICRcXHRpbWVzJCBRdWVzdGlvbiB0eXBlOiBtdWx0aXBsZSBjaG9pY2UiLAogICAgICAgICAgICAgICAgICAgIlBlcmlvZDogcG9zdC1sb2NrZG93biAkXFx0aW1lcyQgUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIiwKICAgICAgICAgICAgICAgICAgICJTY2hvb2wgeWVhcjogMTgvMTkgJFxcdGltZXMkIFF1ZXN0aW9uIHR5cGU6IG11bHRpcGxlIGNob2ljZSIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBsb2NrZG93biAkXFx0aW1lcyQgU2Nob29sIHllYXI6IDE4LzE5ICRcXHRpbWVzJCBRdWVzdGlvbiB0eXBlOiBtdWx0aXBsZSBjaG9pY2UiLAogICAgICAgICAgICAgICAgICAgIlBlcmlvZDogcG9zdC1sb2NrZG93biAkXFx0aW1lcyQgU2Nob29sIHllYXI6IDE4LzE5ICRcXHRpbWVzJCBRdWVzdGlvbiB0eXBlOiBtdWx0aXBsZSBjaG9pY2UiKQoKIyBGb3JtYXQgcC12YWx1ZXMKbV9hY2NfY29lZiRgUHIoPnx6fClgIDwtIGZvcm1hdC5wdmFsKG1fYWNjX2NvZWYkYFByKD58enwpYCwgZXBzID0gLjAwMSwgZGlnaXRzID0gMywgZmxhZyA9ICIwIikKbV9hY2NfY29lZiRgUHIoPnx6fClgIDwtIHN1YignXig8KT8wWy5dJywgJ1xcMS4nLCBtX2FjY19jb2VmJGBQcig+fHp8KWApICMgUmVtb3ZlIGxlYWRpbmcgemVybwoKY2F0KGtuaXRyOjprYWJsZShtX2FjY19jb2VmLAogICAgICAgICAgICAgICAgIGFsaWduID0gYygibCIsInIiLCAiciIsICJyIiwgInIiKSwKICAgICAgICAgICAgICAgICBkaWdpdHMgPSBjKE5BLCAzLCAzLCAyLCBOQSksCiAgICAgICAgICAgICAgICAgY29sLm5hbWVzID0gYygiRWZmZWN0IiwgIiRiJCIsICJTRSIsICIkeiQiLCAiJHAkIiksCiAgICAgICAgICAgICAgICAgZm9ybWF0ID0gImxhdGV4IiwKICAgICAgICAgICAgICAgICBib29rdGFicyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgZXNjYXBlID0gRkFMU0UpLAogICAgZmlsZSA9ICIuLi9vdXRwdXQvbV9hY2NfdGFibGUudGV4IikKYGBgCgpWaXN1YWxpc2UgdGhlIG1vZGVsIGZpdDoKYGBge3J9CmFjY19maXQgPC0gZXhwYW5kLmdyaWQocGVyaW9kID0gYygicHJlLWxvY2tkb3duIiwgImR1cmluZy1sb2NrZG93biIsICJwb3N0LWxvY2tkb3duIiksIHNjaG9vbF95ZWFyID0gYygiMTgvMTkiLCAiMTkvMjAiKSwgbWNxID0gYyhUUlVFLCBGQUxTRSkpCmFjY19maXQgPC0gY2JpbmQoYWNjX2ZpdCwgYWNjdXJhY3kgPSBwcmVkaWN0KG1fYWNjLCB0eXBlID0gInJlc3BvbnNlIiwgcmUuZm9ybSA9IE5BLCBuZXdkYXRhID0gYWNjX2ZpdCkpCmFjY19maXQKCmdncGxvdChhY2NfZml0LCBhZXMoeCA9IHBlcmlvZCwgeSA9IGFjY3VyYWN5LCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgbHR5ID0gbWNxLCBncm91cCA9IGludGVyYWN0aW9uKG1jcSwgc2Nob29sX3llYXIpKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKC43LCAxKSwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArCiAgdGhlbWVfcGFwZXIKYGBgCgpFbXBpcmljYWwgbWVhbnM6CmBgYHtyfQphY2N1cmFjeV9tZWFuIDwtIGFjY3VyYWN5Wyhjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbWNxID09IFRSVUUpIHwgY291cnNlID09ICJGcmVuY2giLCAuKGFjY3VyYWN5ID0gc3VtKGFjY3VyYWN5ICogbikvc3VtKG4pKSwgYnkgPSAuKHBlcmlvZCwgc2Nob29sX3llYXIsIG1jcSwgdXNlciwgY291cnNlKV1bLCAuKGFjY3VyYWN5ID0gbWVhbihhY2N1cmFjeSksIGFjY3VyYWN5X3NkID0gc2QoYWNjdXJhY3kpKSwgYnkgPSAuKHBlcmlvZCwgc2Nob29sX3llYXIsIG1jcSldCmFjY3VyYWN5X21lYW4KYGBgCgpgYGB7cn0KZ2dwbG90KGFjY3VyYWN5X21lYW4sIGFlcyh4ID0gcGVyaW9kLCB5ID0gYWNjdXJhY3ksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBsdHkgPSBtY3EsIGdyb3VwID0gaW50ZXJhY3Rpb24obWNxLCBzY2hvb2xfeWVhcikpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoLjcsIDEpLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCkpICsKICB0aGVtZV9wYXBlcgpgYGAKCgoKCiMjIFJlc3BvbnNlIHRpbWUKCmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKcnQgPC0gZGJHZXRRdWVyeShkYiwgCiAgICAgICAgICAgICAgICAgIlNFTEVDVCByLm1ldGhvZCBBUyAnbWV0aG9kJywKICAgICAgICAgICAgICAgICAgci5ib29rX2luZm9faWQgQVMgJ2Jvb2tfaW5mb19pZCcsCiAgICAgICAgICAgICAgICAgIERBVEUoci5kYXRlICsgMzYwMCwgJ3VuaXhlcG9jaCcpIEFTICdkb3knLAogICAgICAgICAgICAgICAgICByLnVzZXJfaWQgQVMgJ3VzZXInLAogICAgICAgICAgICAgICAgICByLmNob2ljZXMgPiAxIEFTICdtY3EnLAogICAgICAgICAgICAgICAgICByLnJ0IEFTICdydCcKICAgICAgICAgICAgICAgICAgRlJPTSAncmVzcG9uc2VzX25vZHVwbGljYXRlcycgcgogICAgICAgICAgICAgICAgICBXSEVSRSByLnN0dWR5ID09IDAKICAgICAgICAgICAgICAgICAgQU5EIHIuY29ycmVjdCA9PSAxCiAgICAgICAgICAgICAgICAgIgopCnNldERUKHJ0KQpkYl9kaXNjb25uZWN0KGRiKQpgYGAKCmBgYHtyfQpkb3lzIDwtIHJ0WywgLihkb3kgPSB1bmlxdWUoZG95KSldWywgZG95X3Bvc2l4IDo9IGFzLlBPU0lYY3QoZG95KV1bXQpkb3lzWywgZG95X3Bvc2l4X3dlZWsgOj0gY3V0LlBPU0lYdChhcy5QT1NJWGN0KGRveSksICJ3ZWVrIildCmRveXNbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KZG95c1tzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeCArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KZG95c1tzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCBkb3lfcG9zaXhfYWxpZ25lZCA6PSBkb3lfcG9zaXhdCmRveXNbLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrIDo9IGN1dC5QT1NJWHQoZG95X3Bvc2l4X2FsaWduZWQsICJ3ZWVrIildCmRveXNbLCBwZXJpb2QgOj0gZHBseXI6OmNhc2Vfd2hlbigKICBkb3lfcG9zaXhfYWxpZ25lZCA+PSBkYXRlX3NjaG9vbHNfb3BlbmVkIH4gInBvc3QtbG9ja2Rvd24iLAogIGRveV9wb3NpeF9hbGlnbmVkID49IGRhdGVfc2Nob29sc19jbG9zZWQgJiBkb3lfcG9zaXhfYWxpZ25lZCA8IGRhdGVfc2Nob29sc19vcGVuZWQgfiAiZHVyaW5nLWxvY2tkb3duIiwKICBkb3lfcG9zaXhfYWxpZ25lZCA8IGRhdGVfc2Nob29sc19vcGVuZWQgfiAicHJlLWxvY2tkb3duIgopXQoKIyBSZW9yZGVyIGZhY3RvciBsZXZlbHMgc28gdGhhdCBpbnRlcmNlcHQgaXMgcHJlLWxvY2tkb3duIGluIDE5LzIwCmRveXNbLCBwZXJpb2QgOj0gZmFjdG9yKHBlcmlvZCwgbGV2ZWxzID0gYygicHJlLWxvY2tkb3duIiwgImR1cmluZy1sb2NrZG93biIsICJwb3N0LWxvY2tkb3duIikpXQpkb3lzWywgc2Nob29sX3llYXIgOj0gZmFjdG9yKHNjaG9vbF95ZWFyLCBsZXZlbHMgPSBjKCIxOS8yMCIsICIxOC8xOSIpKV0KYGBgCgpgYGB7cn0KcnQgPC0gcnRbZG95cywgb24gPSAiZG95Il0KYGBgCgpgYGB7cn0KcnRbLCBtY3EgOj0gYXMuZmFjdG9yKGFzLmxvZ2ljYWwobWNxKSldCmBgYAoKYGBge3J9CnJ0WywgY291cnNlIDo9IGlmZWxzZShtZXRob2QgPT0gIkdyYW5kZXMgTGlnbmVzIiwgIkZyZW5jaCIsIGlmZWxzZShtZXRob2QgPT0gIlN0ZXBwaW5nIFN0b25lcyIsICJFbmdsaXNoIiwgIkdlcm1hbiIpKV0KYGBgCgpUaHJvdyBvdXQgdHJpYWxzIHdpdGggbmVnYXRpdmUgUlRzICh0aW1pbmcgZXJyb3JzKQpgYGB7cn0KcnQgPC0gcnRbcnQgPiAwXQpgYGAKCgoKIyMjIFdob2xlIHBvcHVsYXRpb24KCmBgYHtyfQpydF9tZWQgPC0gcnRbLCAuKHJ0X21lZGlhbiA9IG1lZGlhbihydCkpLCBieSA9IC4oc2Nob29sX3llYXIsIG1jcSwgdXNlciwgY291cnNlLCBkb3lfcG9zaXhfd2VlayldCgpydF9ieV93ZWVrIDwtIHJ0X21lZFssIC4ocnQgPSBtZWFuKHJ0X21lZGlhbiksIHJ0X3NlID0gc2QocnRfbWVkaWFuKS9zcXJ0KC5OKSksIGJ5ID0gLihzY2hvb2xfeWVhciwgbWNxLCBjb3Vyc2UsIGRveV9wb3NpeF93ZWVrKV0KYGBgCgpPdmVybGFwIHRoZSB0d28gc2Nob29sIHllYXJzOgpgYGB7cn0KcnRfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfd2Vla19hbGlnbmVkIDo9IGFzLlBPU0lYY3QoYXMuUE9TSVhjdChkb3lfcG9zaXhfd2VlaykgKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCnJ0X2J5X3dlZWtbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X3dlZWtfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeF93ZWVrKV0KYGBgCgpBZGQgcXVlc3Rpb24gdHlwZSBsYWJlbHM6CmBgYHtyfQpydF9ieV93ZWVrWywgcXVlc3Rpb25fdHlwZSA6PSBpZmVsc2UobWNxID09IFRSVUUsICJNdWx0aXBsZVxuY2hvaWNlIiwgIk9wZW5cbmFuc3dlciIpXQpgYGAKCmBgYHtyfQpydF9ieV93ZWVrWywgc2Nob29sX3llYXIgOj0gZmFjdG9yKHNjaG9vbF95ZWFyLCBsZXZlbHMgPSBjKCIxOC8xOSIsICIxOS8yMCIpKV0KYGBgCgoKUGxvdCByZXNwb25zZSB0aW1lIGJ5IHdlZWsgKG1lYW4gKy8tIDEgU0UpLgpgYGB7cn0KcF9ydCA8LSBnZ3Bsb3QocnRfYnlfd2Vla1soY291cnNlID09ICJFbmdsaXNoIiAmIG1jcSA9PSBUUlVFKSB8IGNvdXJzZSA9PSAiRnJlbmNoIixdLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF93ZWVrX2FsaWduZWQsIHkgPSBydC8xZTMsIGdyb3VwID0gaW50ZXJhY3Rpb24oc2Nob29sX3llYXIsIHF1ZXN0aW9uX3R5cGUpLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoLiB+IGNvdXJzZSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAwLCB5bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMiwgYWxwaGEgPSAuOSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcnQvMWUzIC0gcnRfc2UvMWUzLCB5bWF4ID0gcnQvMWUzICsgcnRfc2UvMWUzLCBjb2xvdXIgPSBOVUxMKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX2xpbmUoYWVzKGx0eSA9IHF1ZXN0aW9uX3R5cGUpKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICJzIiwgYWNjdXJhY3kgPSAuMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMS43LCAzLjcpKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlJlc3BvbnNlIHRpbWUiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiLAogICAgICAgbHR5ID0gIlF1ZXN0aW9uIHR5cGUiKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBmaWxsID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGx0eSA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDIpKSArCiAgdGhlbWVfcGFwZXIKCnBfcnQKCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGUucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzKQpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlLmVwcyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKZ2dzYXZlKCIuLi9vdXRwdXQvcnRfYnlfcXVlc3Rpb25fdHlwZS5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmBgYAoKIyMjIEJ5IGxldmVsIGFuZCB5ZWFyCgpBZGQgYm9vayBpbmZvOgpgYGB7cn0KcnRbYm9va19pbmZvW2Jvb2tfdHlwZSA9PSAiSG9vZmRib2VrIixdLCBvbiAgPSAiYm9va19pbmZvX2lkIiwgYygibWV0aG9kX2dyb3VwIiwgImJvb2tfdGl0bGUiKSA6PSAuKGkubWV0aG9kX2dyb3VwLCBpLmJvb2tfdGl0bGUpXQpgYGAKClNpbXBsaWZ5IGxldmVsIG5hbWVzOgpgYGB7cn0KIyBLZWVwIGFsbCBkaXN0aW5jdGlvbnMKcnRbLCBib29rX3RpdGxlX3NpbXBsZSA6PSBzdHJpbmdyOjpzdHJfc3ViKGJvb2tfdGl0bGUsIDMsIC0xMCldCnJ0WywgYm9va190aXRsZV9zaW1wbGUgOj0gZmFjdG9yKGJvb2tfdGl0bGVfc2ltcGxlLCBsZXZlbHMgPSBjKCJ2bWJvIGIvbHdvbyIsICJ2bWJvIGIiLCAidm1ibyBiayIsICJ2bWJvIGsiLCAidm1ibyBrZ3QiLCAidm1iby1ndCIsICJ2bWJvIGd0IiwgInZtYm8tZ3QvaGF2byIsICJ2bWJvICh0KWh2IiwgImhhdm8iLCAiaGF2byB2d28iLCAidndvIikpXQoKIyBTaW1wbGlmeSB0byB0aHJlZSBsZXZlbHMKcnRbLCBsZXZlbCA6PSBkcGx5cjo6Y2FzZV93aGVuKAogIGdyZXBsKCAiaHYiLCBib29rX3RpdGxlKSB+ICJHZW5lcmFsIHNlY29uZGFyeVxuKGhhdm8pIiwKICBncmVwbCgidm1ibyIsIGJvb2tfdGl0bGUpIH4gIlByZS12b2NhdGlvbmFsXG4odm1ibykiLAogIGdyZXBsKCJoYXZvIiwgYm9va190aXRsZSkgfiAiR2VuZXJhbCBzZWNvbmRhcnlcbihoYXZvKSIsCiAgZ3JlcGwoInZ3byIsIGJvb2tfdGl0bGUpIH4gIlByZS11bml2ZXJzaXR5XG4odndvKSIsCiAgVFJVRSB+ICJPdGhlciIpXQpydFssIGxldmVsIDo9IGZhY3RvcihsZXZlbCwgbGV2ZWxzID0gYygiT3RoZXIiLCAiUHJlLXZvY2F0aW9uYWxcbih2bWJvKSIsICJHZW5lcmFsIHNlY29uZGFyeVxuKGhhdm8pIiwgIlByZS11bml2ZXJzaXR5XG4odndvKSIpKV0KYGBgCgpTaW1wbGlmeSB5ZWFyIG5hbWVzOgpgYGB7cn0KcnRbLCB5ZWFyIDo9IGRwbHlyOjpjYXNlX3doZW4oCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAxICg1ZSBFZC4pIiB+ICJZZWFyIDEiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMiAoNWUgRWQuKSIgfiAiWWVhciAyIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDMgKDVlIEVkLikiIH4gIlllYXIgMyIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAzLzQgKDVlIEVkLikiIH4gIlllYXIgMy80IiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDQgKDVlIEVkLikiIH4gIlllYXIgNCIsCiAgbWV0aG9kX2dyb3VwID09ICJUd2VlZGUgRmFzZSAoNmUgRWQuKSIgfiAiVHdlZWRlIEZhc2UiLAogIFRSVUUgfiAiT3RoZXIiKV0KYGBgCgoKYGBge3J9CnJ0X3N0cmF0X21lZCA8LSBydFssIC4ocnRfbWVkaWFuID0gbWVkaWFuKHJ0KSksIGJ5ID0gLihzY2hvb2xfeWVhciwgbWNxLCB1c2VyLCBjb3Vyc2UsIGxldmVsLCB5ZWFyLCBkb3lfcG9zaXhfd2VlayldCgpydF9zdHJhdF9ieV93ZWVrIDwtIHJ0X3N0cmF0X21lZFssIC4ocnQgPSBtZWFuKHJ0X21lZGlhbiksIHJ0X3NlID0gc2QocnRfbWVkaWFuKS9zcXJ0KC5OKSksIGJ5ID0gLihzY2hvb2xfeWVhciwgbWNxLCBjb3Vyc2UsIGxldmVsLCB5ZWFyLCBkb3lfcG9zaXhfd2VlayldCmBgYAoKT3ZlcmxhcCB0aGUgdHdvIHNjaG9vbCB5ZWFyczoKYGBge3J9CnJ0X3N0cmF0X2J5X3dlZWtbc2Nob29sX3llYXIgPT0gIjE4LzE5IiwgZG95X3Bvc2l4X3dlZWtfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGFzLlBPU0lYY3QoZG95X3Bvc2l4X3dlZWspICsgMzY1KjI0KjYwKjYwLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpXQpydF9zdHJhdF9ieV93ZWVrW3NjaG9vbF95ZWFyID09ICIxOS8yMCIsIGRveV9wb3NpeF93ZWVrX2FsaWduZWQgOj0gYXMuUE9TSVhjdChkb3lfcG9zaXhfd2VlayldCmBgYAoKQWRkIHF1ZXN0aW9uIHR5cGUgbGFiZWxzOgpgYGB7cn0KcnRfc3RyYXRfYnlfd2Vla1ssIHF1ZXN0aW9uX3R5cGUgOj0gaWZlbHNlKG1jcSA9PSBUUlVFLCAiTXVsdGlwbGVcbmNob2ljZSIsICJPcGVuXG5hbnN3ZXIiKV0KYGBgCgpgYGB7cn0KcnRfc3RyYXRfYnlfd2Vla1ssIHNjaG9vbF95ZWFyIDo9IGZhY3RvcihzY2hvb2xfeWVhciwgbGV2ZWxzID0gYygiMTgvMTkiLCAiMTkvMjAiKSldCmBgYAoKClBsb3QgcmVzcG9uc2UgdGltZSBieSB3ZWVrIChtZWFuICsvLSAxIFNFKS4KYGBge3J9CnBfcnRfbGV2ZWxfeWVhciA8LSBnZ3Bsb3QocnRfc3RyYXRfYnlfd2Vla1tjb3Vyc2UgPT0gIkZyZW5jaCIsXSwKICAgICAgICAgICAgYWVzKHggPSBkb3lfcG9zaXhfd2Vla19hbGlnbmVkLCB5ID0gcnQvMWUzLCBncm91cCA9IGludGVyYWN0aW9uKHNjaG9vbF95ZWFyLCBxdWVzdGlvbl90eXBlKSwgY29sb3VyID0gc2Nob29sX3llYXIsIGZpbGwgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKGxldmVsIH4geWVhcikgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAwLCB5bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTIiLCBjb2xvdXIgPSAiZ3JleTUwIiwgbHR5ID0gMiwgYWxwaGEgPSAuOSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcnQvMWUzIC0gcnRfc2UvMWUzLCB5bWF4ID0gcnQvMWUzICsgcnRfc2UvMWUzLCBjb2xvdXIgPSBOVUxMKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX2xpbmUoYWVzKGx0eSA9IHF1ZXN0aW9uX3R5cGUpKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6dW5pdF9mb3JtYXQodW5pdCA9ICJzIiwgYWNjdXJhY3kgPSAuMSkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMSwgNCkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiUmVzcG9uc2UgdGltZSIsCiAgICAgICBjb2xvdXIgPSAiU2Nob29sIHllYXIiLAogICAgICAgZmlsbCA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBsdHkgPSAiUXVlc3Rpb24gdHlwZSIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGZpbGwgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgbHR5ID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMikpICsKICB0aGVtZV9wYXBlcgoKcF9ydF9sZXZlbF95ZWFyCgpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlX2ZyZW5jaF9sZXZlbF95ZWFyLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKZ2dzYXZlKCIuLi9vdXRwdXQvcnRfYnlfcXVlc3Rpb25fdHlwZV9mcmVuY2hfbGV2ZWxfeWVhci5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfZnJlbmNoX2xldmVsX3llYXIucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzKQpgYGAKCmBgYHtyfQpwX3J0X2xldmVsX3llYXIgPC0gZ2dwbG90KHJ0X3N0cmF0X2J5X3dlZWtbY291cnNlID09ICJFbmdsaXNoIiAmIHF1ZXN0aW9uX3R5cGUgPT0gIk11bHRpcGxlXG5jaG9pY2UiICYgbGV2ZWwgIT0gIk90aGVyIixdLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF93ZWVrX2FsaWduZWQsIHkgPSBydC8xZTMsIGdyb3VwID0gaW50ZXJhY3Rpb24oc2Nob29sX3llYXIsIHF1ZXN0aW9uX3R5cGUpLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiB5ZWFyKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IDAsIHltYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyLCBhbHBoYSA9IC45KSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBydC8xZTMgLSBydF9zZS8xZTMsIHltYXggPSBydC8xZTMgKyBydF9zZS8xZTMsIGNvbG91ciA9IE5VTEwpLCBhbHBoYSA9IDAuMikgKwogIGdlb21fbGluZShhZXMobHR5ID0gcXVlc3Rpb25fdHlwZSkpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gInMiLCBhY2N1cmFjeSA9IC4xKSkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygxLCA0KSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJSZXNwb25zZSB0aW1lIiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGx0eSA9ICJRdWVzdGlvbiB0eXBlIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgZmlsbCA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBsdHkgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAyKSkgKwogIHRoZW1lX3BhcGVyCgpwX3J0X2xldmVsX3llYXIKCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfZW5nbGlzaF9sZXZlbF95ZWFyLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMykKZ2dzYXZlKCIuLi9vdXRwdXQvcnRfYnlfcXVlc3Rpb25fdHlwZV9lbmdsaXNoX2xldmVsX3llYXIuZXBzIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzKQpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlX2VuZ2xpc2hfbGV2ZWxfeWVhci5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmBgYAoKIyMjIEJ5IGxldmVsCgpgYGB7cn0KcnRfbGV2ZWxfbWVkIDwtIHJ0WywgLihydF9tZWRpYW4gPSBtZWRpYW4ocnQpKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBtY3EsIHVzZXIsIGNvdXJzZSwgbGV2ZWwsIGRveV9wb3NpeF93ZWVrKV0KCnJ0X2xldmVsX2J5X3dlZWsgPC0gcnRfbGV2ZWxfbWVkWywgLihydCA9IG1lYW4ocnRfbWVkaWFuKSwgcnRfc2UgPSBzZChydF9tZWRpYW4pL3NxcnQoLk4pKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBtY3EsIGNvdXJzZSwgbGV2ZWwsIGRveV9wb3NpeF93ZWVrKV0KYGBgCgpPdmVybGFwIHRoZSB0d28gc2Nob29sIHllYXJzOgpgYGB7cn0KcnRfbGV2ZWxfYnlfd2Vla1tzY2hvb2xfeWVhciA9PSAiMTgvMTkiLCBkb3lfcG9zaXhfd2Vla19hbGlnbmVkIDo9IGFzLlBPU0lYY3QoYXMuUE9TSVhjdChkb3lfcG9zaXhfd2VlaykgKyAzNjUqMjQqNjAqNjAsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIildCnJ0X2xldmVsX2J5X3dlZWtbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X3dlZWtfYWxpZ25lZCA6PSBhcy5QT1NJWGN0KGRveV9wb3NpeF93ZWVrKV0KYGBgCgpBZGQgcXVlc3Rpb24gdHlwZSBsYWJlbHM6CmBgYHtyfQpydF9sZXZlbF9ieV93ZWVrWywgcXVlc3Rpb25fdHlwZSA6PSBpZmVsc2UobWNxID09IFRSVUUsICJNdWx0aXBsZVxuY2hvaWNlIiwgIk9wZW5cbmFuc3dlciIpXQpgYGAKCmBgYHtyfQpydF9sZXZlbF9ieV93ZWVrWywgc2Nob29sX3llYXIgOj0gZmFjdG9yKHNjaG9vbF95ZWFyLCBsZXZlbHMgPSBjKCIxOC8xOSIsICIxOS8yMCIpKV0KYGBgCgoKUGxvdCByZXNwb25zZSB0aW1lIGJ5IHdlZWsgKG1lYW4gKy8tIDEgU0UpLgpgYGB7cn0KcF9ydF9sZXZlbCA8LSBnZ3Bsb3QocnRfbGV2ZWxfYnlfd2Vla1soKGNvdXJzZSA9PSAiRW5nbGlzaCIgJiBxdWVzdGlvbl90eXBlID09ICJNdWx0aXBsZVxuY2hvaWNlIikgfCBjb3Vyc2UgPT0gIkZyZW5jaCIpICYgbGV2ZWwgIT0gIk90aGVyIixdLAogICAgICAgICAgICBhZXMoeCA9IGRveV9wb3NpeF93ZWVrX2FsaWduZWQsIHkgPSBydC8xZTMsIGdyb3VwID0gaW50ZXJhY3Rpb24oc2Nob29sX3llYXIsIHF1ZXN0aW9uX3R5cGUpLCBjb2xvdXIgPSBzY2hvb2xfeWVhciwgZmlsbCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiBjb3Vyc2UpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gMCwgeW1heCA9IDEwMDAsIGZpbGwgPSAiZ3JleTkyIiwgY29sb3VyID0gImdyZXk1MCIsIGx0eSA9IDIsIGFscGhhID0gLjkpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IHJ0LzFlMyAtIHJ0X3NlLzFlMywgeW1heCA9IHJ0LzFlMyArIHJ0X3NlLzFlMywgY29sb3VyID0gTlVMTCksIGFscGhhID0gMC4yKSArCiAgZ2VvbV9saW5lKGFlcyhsdHkgPSBxdWVzdGlvbl90eXBlKSkgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnVuaXRfZm9ybWF0KHVuaXQgPSAicyIsIGFjY3VyYWN5ID0gLjEpKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDEsIDYpKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChlbmQgPSAuNSwgZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlJlc3BvbnNlIHRpbWUiLAogICAgICAgY29sb3VyID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGZpbGwgPSAiU2Nob29sIHllYXIiLAogICAgICAgbHR5ID0gIlF1ZXN0aW9uIHR5cGUiKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBmaWxsID0gZ3VpZGVfbGVnZW5kKG9yZGVyID0gMSksCiAgICAgICAgIGx0eSA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDIpKSArCiAgdGhlbWVfcGFwZXIKCnBfcnRfbGV2ZWwKCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfbGV2ZWwucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIi4uL291dHB1dC9ydF9ieV9xdWVzdGlvbl90eXBlX2xldmVsLmVwcyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNSkKZ2dzYXZlKCIuLi9vdXRwdXQvcnRfYnlfcXVlc3Rpb25fdHlwZV9sZXZlbC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmBgYAoKIyMjIEJ5IHllYXIKCmBgYHtyfQpydF95ZWFyX21lZCA8LSBydFssIC4ocnRfbWVkaWFuID0gbWVkaWFuKHJ0KSksIGJ5ID0gLihzY2hvb2xfeWVhciwgbWNxLCB1c2VyLCBjb3Vyc2UsIHllYXIsIGRveV9wb3NpeF93ZWVrKV0KCnJ0X3llYXJfYnlfd2VlayA8LSBydF95ZWFyX21lZFssIC4ocnQgPSBtZWFuKHJ0X21lZGlhbiksIHJ0X3NlID0gc2QocnRfbWVkaWFuKS9zcXJ0KC5OKSksIGJ5ID0gLihzY2hvb2xfeWVhciwgbWNxLCBjb3Vyc2UsIHllYXIsIGRveV9wb3NpeF93ZWVrKV0KYGBgCgpPdmVybGFwIHRoZSB0d28gc2Nob29sIHllYXJzOgpgYGB7cn0KcnRfeWVhcl9ieV93ZWVrW3NjaG9vbF95ZWFyID09ICIxOC8xOSIsIGRveV9wb3NpeF93ZWVrX2FsaWduZWQgOj0gYXMuUE9TSVhjdChhcy5QT1NJWGN0KGRveV9wb3NpeF93ZWVrKSArIDM2NSoyNCo2MCo2MCwgb3JpZ2luID0gIjE5NzAtMDEtMDEiKV0KcnRfeWVhcl9ieV93ZWVrW3NjaG9vbF95ZWFyID09ICIxOS8yMCIsIGRveV9wb3NpeF93ZWVrX2FsaWduZWQgOj0gYXMuUE9TSVhjdChkb3lfcG9zaXhfd2VlayldCmBgYAoKQWRkIHF1ZXN0aW9uIHR5cGUgbGFiZWxzOgpgYGB7cn0KcnRfeWVhcl9ieV93ZWVrWywgcXVlc3Rpb25fdHlwZSA6PSBpZmVsc2UobWNxID09IFRSVUUsICJNdWx0aXBsZVxuY2hvaWNlIiwgIk9wZW5cbmFuc3dlciIpXQpgYGAKCmBgYHtyfQpydF95ZWFyX2J5X3dlZWtbLCBzY2hvb2xfeWVhciA6PSBmYWN0b3Ioc2Nob29sX3llYXIsIGxldmVscyA9IGMoIjE4LzE5IiwgIjE5LzIwIikpXQpgYGAKCgpQbG90IHJlc3BvbnNlIHRpbWUgYnkgd2VlayAobWVhbiArLy0gMSBTRSkuCmBgYHtyfQpwX3J0X3llYXIgPC0gZ2dwbG90KHJ0X3llYXJfYnlfd2Vla1soKGNvdXJzZSA9PSAiRW5nbGlzaCIgJiBxdWVzdGlvbl90eXBlID09ICJNdWx0aXBsZVxuY2hvaWNlIikgfCBjb3Vyc2UgPT0gIkZyZW5jaCIpICYgeWVhciAhPSAiT3RoZXIiLF0sCiAgICAgICAgICAgIGFlcyh4ID0gZG95X3Bvc2l4X3dlZWtfYWxpZ25lZCwgeSA9IHJ0LzFlMywgZ3JvdXAgPSBpbnRlcmFjdGlvbihzY2hvb2xfeWVhciwgcXVlc3Rpb25fdHlwZSksIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBmaWxsID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZCh5ZWFyIH4gY291cnNlKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IDAsIHltYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MiIsIGNvbG91ciA9ICJncmV5NTAiLCBsdHkgPSAyLCBhbHBoYSA9IC45KSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBydC8xZTMgLSBydF9zZS8xZTMsIHltYXggPSBydC8xZTMgKyBydF9zZS8xZTMsIGNvbG91ciA9IE5VTEwpLCBhbHBoYSA9IDAuMikgKwogIGdlb21fbGluZShhZXMobHR5ID0gcXVlc3Rpb25fdHlwZSkpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjp1bml0X2Zvcm1hdCh1bml0ID0gInMiLCBhY2N1cmFjeSA9IC4xKSkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygxLCA0KSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZW5kID0gLjUsIGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJSZXNwb25zZSB0aW1lIiwKICAgICAgIGNvbG91ciA9ICJTY2hvb2wgeWVhciIsCiAgICAgICBmaWxsID0gIlNjaG9vbCB5ZWFyIiwKICAgICAgIGx0eSA9ICJRdWVzdGlvbiB0eXBlIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAxKSwKICAgICAgICAgZmlsbCA9IGd1aWRlX2xlZ2VuZChvcmRlciA9IDEpLAogICAgICAgICBsdHkgPSBndWlkZV9sZWdlbmQob3JkZXIgPSAyKSkgKwogIHRoZW1lX3BhcGVyCgpwX3J0X3llYXIKCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfeWVhci5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfeWVhci5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmdnc2F2ZSgiLi4vb3V0cHV0L3J0X2J5X3F1ZXN0aW9uX3R5cGVfeWVhci5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDUpCmBgYAoKCiMjIyBSZWdyZXNzaW9uIG1vZGVsCgpgYGB7cn0KcnRfbW9kZWxfZGF0IDwtIHJ0WywgLihydF9tZWRpYW4gPSBtZWRpYW4ocnQpKSwgYnkgPSAuKGNvdXJzZSwgc2Nob29sX3llYXIsIHBlcmlvZCwgZG95X3Bvc2l4LCBtY3EsIHVzZXIpXQpgYGAKCkZpdCBhIGdlbmVyYWxpc2VkIGxpbmVhciBtaXhlZCBlZmZlY3RzIG1vZGVsIChhc3N1bWluZyBhIEdhbW1hIGRpc3RyaWJ1dGlvbiBmb3IgUlQgYW5kIGFuIGlkZW50aXR5IGxpbmsgZnVuY3Rpb247IExvICYgQW5kcmV3LCAyMDE1KSB0byB0aGUgZGFpbHkgbWVkaWFuIFJUOgpgYGB7cn0KaWYoIWZpbGUuZXhpc3RzKCIuLi9vdXRwdXQvbV9ydF9maXQucmRzIikpIHsKICBtX3J0IDwtIGdsbWVyKHJ0X21lZGlhbiB+IHBlcmlvZCpzY2hvb2xfeWVhciptY3EgKyAoMSB8IHVzZXIpICsgKDEgfCBjb3Vyc2UpLAogICAgICAgICAgICAgICAgIGRhdGEgPSBydF9tb2RlbF9kYXRbKGNvdXJzZSA9PSAiRW5nbGlzaCIgJiBtY3EgPT0gVFJVRSkgfCBjb3Vyc2UgPT0gIkZyZW5jaCIsXSwKICAgICAgICAgICAgICAgICBmYW1pbHkgPSBHYW1tYShsaW5rID0gImlkZW50aXR5IiksCiAgICAgICAgICAgICAgICAgbkFHUSA9IDAsCiAgICAgICAgICAgICAgICAgY29udHJvbCA9IGdsbWVyQ29udHJvbChvcHRpbWl6ZXIgPSAiYm9ieXFhIiwgb3B0Q3RybCA9IGxpc3QobWF4ZnVuID0gMWU2KSkpCiAgc2F2ZVJEUyhtX3J0LCAiLi4vb3V0cHV0L21fcnRfZml0LnJkcyIpCn0gZWxzZSB7CiAgbV9ydCA8LSByZWFkUkRTKCIuLi9vdXRwdXQvbV9ydF9maXQucmRzIikKfQoKbV9ydF9zdW1tYXJ5IDwtIHN1bW1hcnkobV9ydCkKbV9ydF9zdW1tYXJ5CmBgYAoKU2F2ZSBjb2VmZmljaWVudHMgYXMgYSB0YWJsZSBmb3IgaW4gdGhlIHBhcGVyOgpgYGB7cn0KbV9ydF9jb2VmIDwtIGFzLmRhdGEuZnJhbWUobV9ydF9zdW1tYXJ5JGNvZWZmaWNpZW50cykKc2V0RFQobV9ydF9jb2VmLCBrZWVwLnJvd25hbWVzID0gVFJVRSkKbV9ydF9jb2VmJHJuIDwtIGMoIkludGVyY2VwdCBcXHNtYWxseyhQZXJpb2Q6IHByZS1sb2NrZG93biwgU2Nob29sIHllYXI6IDE5LzIwLCBRdWVzdGlvbiB0eXBlOiBvcGVuIGFuc3dlcil9IiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IGxvY2tkb3duIiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IHBvc3QtbG9ja2Rvd24iLAogICAgICAgICAgICAgICAgICAgIlNjaG9vbCB5ZWFyOiAxOC8xOSIsCiAgICAgICAgICAgICAgICAgICAiUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IGxvY2tkb3duICRcXHRpbWVzJCBTY2hvb2wgeWVhcjogMTgvMTkiLAogICAgICAgICAgICAgICAgICAgIlBlcmlvZDogcG9zdC1sb2NrZG93biAkXFx0aW1lcyQgU2Nob29sIHllYXI6IDE4LzE5IiwKICAgICAgICAgICAgICAgICAgICJQZXJpb2Q6IGxvY2tkb3duICRcXHRpbWVzJCBRdWVzdGlvbiB0eXBlOiBtdWx0aXBsZSBjaG9pY2UiLAogICAgICAgICAgICAgICAgICAgIlBlcmlvZDogcG9zdC1sb2NrZG93biAkXFx0aW1lcyQgUXVlc3Rpb24gdHlwZTogbXVsdGlwbGUgY2hvaWNlIiwKICAgICAgICAgICAgICAgICAgICJTY2hvb2wgeWVhcjogMTgvMTkgJFxcdGltZXMkIFF1ZXN0aW9uIHR5cGU6IG11bHRpcGxlIGNob2ljZSIsCiAgICAgICAgICAgICAgICAgICAiUGVyaW9kOiBsb2NrZG93biAkXFx0aW1lcyQgU2Nob29sIHllYXI6IDE4LzE5ICRcXHRpbWVzJCBRdWVzdGlvbiB0eXBlOiBtdWx0aXBsZSBjaG9pY2UiLAogICAgICAgICAgICAgICAgICAgIlBlcmlvZDogcG9zdC1sb2NrZG93biAkXFx0aW1lcyQgU2Nob29sIHllYXI6IDE4LzE5ICRcXHRpbWVzJCBRdWVzdGlvbiB0eXBlOiBtdWx0aXBsZSBjaG9pY2UiKQoKIyBGb3JtYXQgcC12YWx1ZXMKbV9ydF9jb2VmJGBQcig+fHp8KWAgPC0gZm9ybWF0LnB2YWwobV9ydF9jb2VmJGBQcig+fHp8KWAsIGVwcyA9IC4wMDEsIGRpZ2l0cyA9IDMsIGZsYWcgPSAiMCIpCm1fcnRfY29lZiRgUHIoPnx6fClgIDwtIHN1YignXig8KT8wWy5dJywgJ1xcMS4nLCBtX3J0X2NvZWYkYFByKD58enwpYCkgIyBSZW1vdmUgbGVhZGluZyB6ZXJvCgpjYXQoa25pdHI6OmthYmxlKG1fcnRfY29lZiwKICAgICAgICAgICAgICAgICBhbGlnbiA9IGMoImwiLCJyIiwgInIiLCAiciIsICJyIiksCiAgICAgICAgICAgICAgICAgZGlnaXRzID0gYyhOQSwgMywgMywgMiwgTkEpLAogICAgICAgICAgICAgICAgIGNvbC5uYW1lcyA9IGMoIkVmZmVjdCIsICIkYiQiLCAiU0UiLCAiJHokIiwgIiRwJCIpLAogICAgICAgICAgICAgICAgIGZvcm1hdCA9ICJsYXRleCIsCiAgICAgICAgICAgICAgICAgYm9va3RhYnMgPSBUUlVFLAogICAgICAgICAgICAgICAgIGVzY2FwZSA9IEZBTFNFKSwKICAgIGZpbGUgPSAiLi4vb3V0cHV0L21fcnRfdGFibGUudGV4IikKYGBgCgpWaXN1YWxpc2UgdGhlIG1vZGVsIGZpdDoKYGBge3J9CnJ0X2ZpdCA8LSBleHBhbmQuZ3JpZChwZXJpb2QgPSBjKCJwcmUtbG9ja2Rvd24iLCAiZHVyaW5nLWxvY2tkb3duIiwgInBvc3QtbG9ja2Rvd24iKSwgc2Nob29sX3llYXIgPSBjKCIxOC8xOSIsICIxOS8yMCIpLCBtY3EgPSBjKFRSVUUsIEZBTFNFKSkKcnRfZml0IDwtIGNiaW5kKHJ0X2ZpdCwgcnQgPSBwcmVkaWN0KG1fcnQsIHR5cGUgPSAicmVzcG9uc2UiLCByZS5mb3JtID0gTkEsIG5ld2RhdGEgPSBydF9maXQpKQpydF9maXQKCmdncGxvdChydF9maXQsIGFlcyh4ID0gcGVyaW9kLCB5ID0gcnQsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBsdHkgPSBtY3EsIGdyb3VwID0gaW50ZXJhY3Rpb24obWNxLCBzY2hvb2xfeWVhcikpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMTUwMCwgNDAwMCkpICsKICB0aGVtZV9wYXBlcgpgYGAKCkVtcGlyaWNhbCBtZWFuczoKYGBge3J9CnJ0X21lYW4gPC0gcnRfbW9kZWxfZGF0Wyhjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbWNxID09IFRSVUUpIHwgY291cnNlID09ICJGcmVuY2giLCAuKHJ0ID0gbWVhbihydF9tZWRpYW4pKSwgYnkgPSAuKHBlcmlvZCwgc2Nob29sX3llYXIsIG1jcSwgdXNlciwgY291cnNlKV1bLCAuKHJ0ID0gbWVhbihydCksIHJ0X3NkID0gc2QocnQpKSwgYnkgPSAuKHBlcmlvZCwgc2Nob29sX3llYXIsIG1jcSwgY291cnNlKV0KcnRfbWVhblssIHNjaG9vbF95ZWFyIDo9IGZhY3RvcihzY2hvb2xfeWVhciwgbGV2ZWxzID0gYygiMTgvMTkiLCAiMTkvMjAiKSldCnJ0X21lYW4KYGBgCgpgYGB7cn0KZ2dwbG90KHJ0X21lYW4sIGFlcyh4ID0gcGVyaW9kLCB5ID0gcnQsIGNvbG91ciA9IHNjaG9vbF95ZWFyLCBsdHkgPSBtY3EsIGdyb3VwID0gaW50ZXJhY3Rpb24obWNxLCBzY2hvb2xfeWVhcikpKSArCiAgZmFjZXRfZ3JpZCh+IGNvdXJzZSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDE1MDAsIDMwMDApKSArCiAgdGhlbWVfcGFwZXIKYGBgCgoKIyMgQ29tYmluYXRpb24gcGxvdAoKYGBge3J9CnBfbGVnZW5kIDwtIGdldF9sZWdlbmQocF9hY2MpCgpwX2FjYyA8LSBwX2FjYyArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFLCBmaWxsID0gRkFMU0UsIGx0eSA9IEZBTFNFKQoKcF9ydCA8LSBwX3J0ICsKICBndWlkZXMoY29sb3VyID0gRkFMU0UsIGZpbGwgPSBGQUxTRSwgbHR5ID0gRkFMU0UpCmBgYAoKQ29tYmluZSBwbG90czoKYGBge3J9CnBsb3RfZ3JpZCgKICBwbG90X2dyaWQocF9hY2MsIHBfcnQsCiAgICAgICAgICAgIG5jb2wgPSAxLAogICAgICAgICAgICBsYWJlbHMgPSBjKCJBIiwgIkIiKSksCiAgcF9sZWdlbmQsCiAgcmVsX3dpZHRocyA9IGMoMSwgLjIpCikKCmdnc2F2ZSgiLi4vb3V0cHV0L2NvbWJpX2FjY19ydC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDMuNSkKZ2dzYXZlKCIuLi9vdXRwdXQvY29tYmlfYWNjX3J0LmVwcyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMy41KQpnZ3NhdmUoIi4uL291dHB1dC9jb21iaV9hY2NfcnQucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSAzLjUpCmBgYAoKCgojIExlYXJuaW5nIHByb2dyZXNzCgpHZXQgdGhlIHVuaXF1ZSBib29rIGNoYXB0ZXIgSURzIG9uIGVhY2ggZGF5OgpgYGB7cn0KZGIgPC0gZGJfY29ubmVjdCgpCgpwcm9ncmVzcyA8LSBkYkdldFF1ZXJ5KGRiLAogICAgICAgICAgICAgICAgICAgICAgICJTRUxFQ1QgRElTVElOQ1Qgci5ib29rX2luZm9faWQgQVMgJ2Jvb2tfaW5mb19pZCcsCiAgICAgICAgICAgICAgICAgICAgICAgIHIubWV0aG9kIEFTICdtZXRob2QnLAogICAgICAgICAgICAgICAgICAgICAgICBEQVRFKHIuZGF0ZSArIDM2MDAsICd1bml4ZXBvY2gnKSBBUyAnZG95JywKICAgICAgICAgICAgICAgICAgICAgICAgQ09VTlQoKikgQVMgJ3RyaWFscycKICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSAncmVzcG9uc2VzX25vZHVwbGljYXRlcycgcgogICAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSByLm1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgci5ib29rX2luZm9faWQsCiAgICAgICAgICAgICAgICAgICAgICAgIERBVEUoci5kYXRlICsgMzYwMCwgJ3VuaXhlcG9jaCcpOyIKICAgICAgICAgICAgICAgICAgICAgICApCgpkYl9kaXNjb25uZWN0KGRiKQoKc2V0RFQocHJvZ3Jlc3MpCmBgYAoKSm9pbiB3aXRoIHRoZSBib29rIGNoYXB0ZXIgaW5mb3JtYXRpb246CmBgYHtyfQpkYiA8LSBkYl9jb25uZWN0KCkKYm9va19pbmZvIDwtIGRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgRElTVElOQ1QgKiBGUk9NICdib29rX2luZm8nIikKZGJfZGlzY29ubmVjdChkYikKCnNldERUKGJvb2tfaW5mbykKYGBgCgpgYGB7cn0KcHJvZ3Jlc3NbYm9va19pbmZvW2Jvb2tfdHlwZSA9PSAiSG9vZmRib2VrIixdLCBvbiAgPSAiYm9va19pbmZvX2lkIiwgYygibWV0aG9kX2dyb3VwIiwgImJvb2tfdGl0bGUiLCAiY2hhcHRlciIpIDo9IC4oaS5tZXRob2RfZ3JvdXAsIGkuYm9va190aXRsZSwgaS5jaGFwdGVyKV0KYGBgCgpBZGQgc2Vuc2libGUgY291cnNlIG5hbWVzOgpgYGB7cn0KcHJvZ3Jlc3NbLCBjb3Vyc2UgOj0gaWZlbHNlKG1ldGhvZCA9PSAiR3JhbmRlcyBMaWduZXMiLCAiRnJlbmNoIiwgaWZlbHNlKG1ldGhvZCA9PSAiU3RlcHBpbmcgU3RvbmVzIiwgIkVuZ2xpc2giLCAiR2VybWFuIikpXQpgYGAKCkFkZCBhIHNjaG9vbCB5ZWFyIGNvbHVtbiAoY3V0b2ZmIGRhdGU6IDEgQXVndXN0KToKYGBge3J9CnByb2dyZXNzWywgZG95X3Bvc2l4IDo9IGFzLlBPU0lYY3QoZG95KV0KcHJvZ3Jlc3NbLCBzY2hvb2xfeWVhciA6PSBpZmVsc2UoZG95X3Bvc2l4IDwgIjIwMTktMDgtMDEiLCAiMTgvMTkiLCAiMTkvMjAiKV0KYGBgCgpDb25zb2xpZGF0ZSBjb3VudCBieSBkYXkgYW5kIGNoYXB0ZXI6CmBgYHtyfQpwcm9ncmVzc19ieV9kYXkgPC0gcHJvZ3Jlc3NbLCAuKHRyaWFscyA9IHN1bSh0cmlhbHMpKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBkb3lfcG9zaXgsIGNvdXJzZSwgbWV0aG9kX2dyb3VwLCBib29rX3RpdGxlLCBjaGFwdGVyKV0KYGBgCgoKU2ltcGxpZnkgbGV2ZWwgbmFtZXM6CmBgYHtyfQojIEtlZXAgYWxsIGRpc3RpbmN0aW9ucwpwcm9ncmVzc19ieV9kYXlbLCBib29rX3RpdGxlX3NpbXBsZSA6PSBzdHJpbmdyOjpzdHJfc3ViKGJvb2tfdGl0bGUsIDMsIC0xMCldCnByb2dyZXNzX2J5X2RheVssIGJvb2tfdGl0bGVfc2ltcGxlIDo9IGZhY3Rvcihib29rX3RpdGxlX3NpbXBsZSwgbGV2ZWxzID0gYygidm1ibyBiL2x3b28iLCAidm1ibyBiIiwgInZtYm8gYmsiLCAidm1ibyBrIiwgInZtYm8ga2d0IiwgInZtYm8tZ3QiLCAidm1ibyBndCIsICJ2bWJvLWd0L2hhdm8iLCAidm1ibyAodClodiIsICJoYXZvIiwgImhhdm8gdndvIiwgInZ3byIpKV0KCiMgU2ltcGxpZnkgdG8gdGhyZWUgbGV2ZWxzCnByb2dyZXNzX2J5X2RheVssIGxldmVsIDo9IGRwbHlyOjpjYXNlX3doZW4oCiAgZ3JlcGwoICJodiIsIGJvb2tfdGl0bGUpIH4gIkdlbmVyYWwgc2Vjb25kYXJ5IChoYXZvKSIsCiAgZ3JlcGwoInZtYm8iLCBib29rX3RpdGxlKSB+ICJQcmUtdm9jYXRpb25hbCAodm1ibykiLAogIGdyZXBsKCJoYXZvIiwgYm9va190aXRsZSkgfiAiR2VuZXJhbCBzZWNvbmRhcnkgKGhhdm8pIiwKICBncmVwbCgidndvIiwgYm9va190aXRsZSkgfiAiUHJlLXVuaXZlcnNpdHkgKHZ3bykiLAogIFRSVUUgfiAiT3RoZXIiKV0KcHJvZ3Jlc3NfYnlfZGF5WywgbGV2ZWwgOj0gZmFjdG9yKGxldmVsLCBsZXZlbHMgPSBjKCJPdGhlciIsICJQcmUtdm9jYXRpb25hbCAodm1ibykiLCAiR2VuZXJhbCBzZWNvbmRhcnkgKGhhdm8pIiwgIlByZS11bml2ZXJzaXR5ICh2d28pIikpXQpgYGAKClNpbXBsaWZ5IHllYXIgbmFtZXM6CmBgYHtyfQpwcm9ncmVzc19ieV9kYXlbLCB5ZWFyIDo9IGRwbHlyOjpjYXNlX3doZW4oCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAxICg1ZSBFZC4pIiB+ICJZZWFyIDEiLAogIG1ldGhvZF9ncm91cCA9PSAiTGVlcmphYXIgMiAoNWUgRWQuKSIgfiAiWWVhciAyIiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDMgKDVlIEVkLikiIH4gIlllYXIgMyIsCiAgbWV0aG9kX2dyb3VwID09ICJMZWVyamFhciAzLzQgKDVlIEVkLikiIH4gIlllYXIgMy80IiwKICBtZXRob2RfZ3JvdXAgPT0gIkxlZXJqYWFyIDQgKDVlIEVkLikiIH4gIlllYXIgNCIsCiAgbWV0aG9kX2dyb3VwID09ICJUd2VlZGUgRmFzZSAoNmUgRWQuKSIgfiAiVHdlZWRlIEZhc2UiLAogIFRSVUUgfiAiT3RoZXIiKV0KYGBgCgoKU2ltcGxpZnkgY2hhcHRlciBuYW1lczoKYGBge3J9CiMgSW4gbW9zdCBjYXNlcywgdGhlIGNoYXB0ZXIgbmFtZSBzdGFydHMgd2l0aCBhIG51bWJlcgpwcm9ncmVzc19ieV9kYXlbLCBjaGFwdGVyX3NpbXBsZSA6PSBmYWN0b3IoYXMubnVtZXJpYyhzdHJpbmdyOjpzdHJfZXh0cmFjdChjaGFwdGVyLCAiXlxcZHsxLDJ9IikpKV0KCiMgUmVtYWluaW5nIGNhc2VzOgp1bmlxdWUocHJvZ3Jlc3NfYnlfZGF5W2lzLm5hKGNoYXB0ZXJfc2ltcGxlKSxdJGNoYXB0ZXIpCgojIENvbWJpbmUgdGhlc2UgY2hhcHRlcnMgaW50byBhbiAib3RoZXIiIGNhdGVnb3J5CnByb2dyZXNzX2J5X2RheVtpcy5uYShjaGFwdGVyX3NpbXBsZSksIGNoYXB0ZXJfc2ltcGxlIDo9ICJPIl0KYGBgCgoKQWxpZ24gc2Nob29sIHllYXJzOgpgYGB7cn0KcHJvZ3Jlc3NfYnlfZGF5W3NjaG9vbF95ZWFyID09ICIxOC8xOSIsIGRveV9wb3NpeF9hbGlnbmVkIDo9IGFzLlBPU0lYY3QoZG95X3Bvc2l4ICsgMzY1KjI0KjYwKjYwLCBvcmlnaW4gPSAiMTk3MC0wMS0wMSIpXQpwcm9ncmVzc19ieV9kYXlbc2Nob29sX3llYXIgPT0gIjE5LzIwIiwgZG95X3Bvc2l4X2FsaWduZWQgOj0gZG95X3Bvc2l4XQpgYGAKClVzZSBjdXQuRGF0ZSgpIHRvIGJpbiBkYXRlcyBieSB3ZWVrIGFuZCBtb250aC4gRWFjaCBkYXkgaXMgYXNzaWduZWQgdGhlIGRhdGUgb2YgdGhlIG1vc3QgcmVjZW50IE1vbmRheS4KYGBge3J9CnByb2dyZXNzX2J5X2RheVssIGRveV9wb3NpeF9hbGlnbmVkX3dlZWsgOj0gY3V0LlBPU0lYdChkb3lfcG9zaXhfYWxpZ25lZCwgIndlZWsiKV0KcHJvZ3Jlc3NfYnlfZGF5WywgZG95X3Bvc2l4X2FsaWduZWRfbW9udGggOj0gY3V0LlBPU0lYdChkb3lfcG9zaXhfYWxpZ25lZCwgIm1vbnRoIildCmBgYAoKQ2FsY3VsYXRlIHByb3BvcnRpb25zIGJ5IHdlZWsgYW5kIG1vbnRoOgpgYGB7cn0KcHJvZ3Jlc3NfYnlfd2VlayA8LSBwcm9ncmVzc19ieV9kYXlbLCAuKHRyaWFscyA9IHN1bSh0cmlhbHMpKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBjb3Vyc2UsIGxldmVsLCB5ZWFyLCBjaGFwdGVyX3NpbXBsZSldCnByb2dyZXNzX2J5X3dlZWtbLCBwcm9wIDo9IHRyaWFscy9zdW0odHJpYWxzKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBkb3lfcG9zaXhfYWxpZ25lZF93ZWVrLCBjb3Vyc2UsIGxldmVsLCB5ZWFyKV0KCnByb2dyZXNzX2J5X21vbnRoIDwtIHByb2dyZXNzX2J5X2RheVssIC4odHJpYWxzID0gc3VtKHRyaWFscykpLCBieSA9IC4oc2Nob29sX3llYXIsIGRveV9wb3NpeF9hbGlnbmVkX21vbnRoLCBjb3Vyc2UsIGxldmVsLCB5ZWFyLCBjaGFwdGVyX3NpbXBsZSldCnByb2dyZXNzX2J5X21vbnRoWywgcHJvcCA6PSB0cmlhbHMvc3VtKHRyaWFscyksIGJ5ID0gLihzY2hvb2xfeWVhciwgZG95X3Bvc2l4X2FsaWduZWRfbW9udGgsIGNvdXJzZSwgbGV2ZWwsIHllYXIpXQpgYGAKCmBgYHtyfQpzZXRvcmRlcihwcm9ncmVzc19ieV93ZWVrLCBjaGFwdGVyX3NpbXBsZSkKc2V0b3JkZXIocHJvZ3Jlc3NfYnlfbW9udGgsIGNoYXB0ZXJfc2ltcGxlKQpgYGAKCgojIyMgRnJlbmNoCmBgYHtyfQpwX2ZyZW5jaF95MSA8LSBnZ3Bsb3QocHJvZ3Jlc3NfYnlfd2Vla1tjb3Vyc2UgPT0gIkZyZW5jaCIgJiB5ZWFyID09ICJZZWFyIDEiXSwgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gcHJvcCwgZmlsbCA9IGNoYXB0ZXJfc2ltcGxlLCBncm91cCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoc2Nob29sX3llYXIgfiBsZXZlbCkgKwogIGdlb21fY29sKGFscGhhID0gMC43NSwgd2lkdGggPSA3KjI0KjYwKjYwLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMC4wMSwgeW1heCA9IDEuMDEsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgbHR5ID0gMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IGMoMCwgLjUgLCAxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAyKSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfZnJlbmNoX3kyIDwtIGdncGxvdChwcm9ncmVzc19ieV93ZWVrW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMiJdLCBhZXMoeCA9IGFzLlBPU0lYY3QoZG95X3Bvc2l4X2FsaWduZWRfd2VlayksIHkgPSBwcm9wLCBmaWxsID0gY2hhcHRlcl9zaW1wbGUsIGdyb3VwID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChzY2hvb2xfeWVhciB+IGxldmVsKSArCiAgZ2VvbV9jb2woYWxwaGEgPSAwLjc1LCB3aWR0aCA9IDcqMjQqNjAqNjAsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0wLjAxLCB5bWF4ID0gMS4wMSwgZmlsbCA9IE5BLCBjb2xvdXIgPSAiYmxhY2siLCBsdHkgPSAyKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLCAKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCksIGxpbWl0cyA9IGMoMCwxKSwgYnJlYWtzID0gYygwLCAuNSAsIDEpKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobmNvbCA9IDIpKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiU2hhcmUgb2YgdHJpYWxzIiwKICAgICAgIGZpbGwgPSAiQ2hhcHRlciIpICsKICB0aGVtZV9wYXBlcgoKcF9mcmVuY2hfeTMgPC0gZ2dwbG90KHByb2dyZXNzX2J5X3dlZWtbY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAzLzQiXSwgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gcHJvcCwgZmlsbCA9IGNoYXB0ZXJfc2ltcGxlLCBncm91cCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoc2Nob29sX3llYXIgfiBsZXZlbCkgKwogIGdlb21fY29sKGFscGhhID0gMC43NSwgd2lkdGggPSA3KjI0KjYwKjYwLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMC4wMSwgeW1heCA9IDEuMDEsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgbHR5ID0gMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IGMoMCwgLjUgLCAxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAyKSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfcHJvZ3Jlc3NfZnJlbmNoIDwtIHBsb3RfZ3JpZChwX2ZyZW5jaF95MSwgcF9mcmVuY2hfeTIsIHBfZnJlbmNoX3kzLCAKICAgICAgICAgIG5jb2wgPSAxLAogICAgICAgICAgYWxpZ24gPSAiaHYiLCBheGlzID0gInRibHIiLAogICAgICAgICAgbGFiZWxzID0gYygiWWVhciAxIiwgIlllYXIgMiIsICJZZWFyIDMvNCIpLAogICAgICAgICAgaGp1c3QgPSAtMC4xLAogICAgICAgICAgc2NhbGUgPSAuOTUpCgpwX3Byb2dyZXNzX2ZyZW5jaAoKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfZnJlbmNoLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfZnJlbmNoLmVwcyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfZnJlbmNoLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNikKYGBgCgoKRGlkIHRoZSBzaGFyZSBvZiB0cmlhbHMgY2hhbmdlIGJldHdlZW4gc2Nob29sIHllYXJzPwpXZSBjYW4gc2ltcGxpZnkgdGhlIGFuYWx5c2lzIGJ5IGFnZ3JlZ2F0aW5nIG92ZXIgdGhlIHdob2xlIGxvY2tkb3duIHBlcmlvZC4KYGBge3J9CnByb2dyZXNzX2xvY2tkb3duIDwtIHByb2dyZXNzX2J5X2RheVtiZXR3ZWVuKGRveV9wb3NpeF9hbGlnbmVkLCBkYXRlX3NjaG9vbHNfY2xvc2VkLCBkYXRlX3NjaG9vbHNfb3BlbmVkKSwgLih0cmlhbHMgPSBzdW0odHJpYWxzKSksIGJ5ID0gLihzY2hvb2xfeWVhciwgY291cnNlLCBsZXZlbCwgeWVhciwgY2hhcHRlcl9zaW1wbGUpXQoKIyBGaWxsIGluIG1pc3Npbmcgcm93cyAob2NjdXJzIHdoZW4gY2hhcHRlciB3YXMgb25seSBzdHVkaWVkIGluIG9uZSBvZiB0aGUgdHdvIHllYXJzKQpwcm9ncmVzc19sb2NrZG93biA8LSBhcy5kYXRhLnRhYmxlKHRpZHlyOjpjb21wbGV0ZShwcm9ncmVzc19sb2NrZG93biwgdGlkeXI6Om5lc3RpbmcoY291cnNlLCBsZXZlbCwgeWVhciwgY2hhcHRlcl9zaW1wbGUpLCBzY2hvb2xfeWVhciwgZmlsbCA9IGxpc3QodHJpYWxzID0gMCkpKSAKICAKcHJvZ3Jlc3NfbG9ja2Rvd25bLCBwcm9wIDo9IHRyaWFscy9zdW0odHJpYWxzKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBjb3Vyc2UsIGxldmVsLCB5ZWFyKV0KCnNldG9yZGVyKHByb2dyZXNzX2xvY2tkb3duLCBjaGFwdGVyX3NpbXBsZSkKYGBgCgpgYGB7cn0KZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duW2NvdXJzZSA9PSAiRnJlbmNoIl0sIGFlcyh4ID0gc2Nob29sX3llYXIsIHkgPSBwcm9wLCBmaWxsID0gY2hhcHRlcl9zaW1wbGUsIGdyb3VwID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChsZXZlbCB+IHllYXIpICsKICBnZW9tX2NvbChjb2xvdXIgPSBOQSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCksIGxpbWl0cyA9IGMoMCwxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiLAogICAgICAgdGl0bGUgPSAiRnJlbmNoIikgKwogIHRoZW1lX3BhcGVyCmBgYAoKUGVyZm9ybSBhIGNoaS1zcXVhcmUgdGVzdCBvZiBob21vZ2VuZWl0eSB0byBkZXRlcm1pbmUgd2hldGhlciBzY2hvb2wgeWVhcnMgYXJlIHNpZ25pZmljYW50bHkgZGlmZmVyZW50LgoKYGBge3J9CmZvciAoeSBpbiBzb3J0KHVuaXF1ZShwcm9ncmVzc19sb2NrZG93biR5ZWFyKSkpIHsKICBmb3IgKGwgaW4gbGV2ZWxzKHByb2dyZXNzX2xvY2tkb3duJGxldmVsKSkgewogICAgZCA8LSBwcm9ncmVzc19sb2NrZG93bltjb3Vyc2UgPT0gIkZyZW5jaCIgJiB5ZWFyID09IHkgJiBsZXZlbCA9PSBsXQogICAgaWYgKG5yb3coZCkgPiAwKSB7CiAgICAgIHByaW50KHBhc3RlKCJGcmVuY2giLCB5LCBsLCBjb2xsYXBzZT0gIiAiKSkKICAgICAgcHJpbnQoCiAgICAgICAgY2hpc3EudGVzdCgKICAgICAgICAgIGRjYXN0KGQsIHNjaG9vbF95ZWFyIH4gY2hhcHRlcl9zaW1wbGUsIHZhbHVlLnZhciA9ICJ0cmlhbHMiLCBmaWxsID0gMClbLCBzY2hvb2xfeWVhciA6PSBOVUxMXQogICAgICAgICkKICAgICAgKQogICAgfQogIH0gCn0KYGBgCgpDb25jbHVzaW9uOiBhbGwgdGVzdHMgaW5kaWNhdGUgYSBkaWZmZXJlbmNlIGluIHByb3BvcnRpb25zIGJldHdlZW4gc2Nob29sIHllYXJzIChwIDw8IDAuMDAxKS4KClZpc3VhbGlzZSB0aGUgY2hhbmdlIGJldHdlZW4gc2Nob29sIHllYXJzOgpgYGB7cn0KcHJvZ3Jlc3NfbG9ja2Rvd25bLCBwcm9wX2NoYW5nZSA6PSBwcm9wW3NjaG9vbF95ZWFyID09ICIxOS8yMCJdIC0gcHJvcFtzY2hvb2xfeWVhciA9PSAiMTgvMTkiXSwgYnkgPSAuKGNvdXJzZSwgbGV2ZWwsIHllYXIsIGNoYXB0ZXJfc2ltcGxlKV0KCmdncGxvdChwcm9ncmVzc19sb2NrZG93bltzY2hvb2xfeWVhciA9PSAiMTkvMjAiICYgY291cnNlID09ICJGcmVuY2giXSwgYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCksIGNvbG91ciA9IGNoYXB0ZXJfc2ltcGxlLCBncm91cCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiB5ZWFyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHhlbmQgPSBjaGFwdGVyX3NpbXBsZSksIHllbmQgPSAwKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygtMjUsIDI1KSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFKSArCiAgbGFicyh4ID0gIkNoYXB0ZXIiLAogICAgICAgeSA9ICJDaGFuZ2UgaW4gdHJpYWwgc2hhcmVcbihwZXJjZW50YWdlIHBvaW50cykiLAogICAgICAgdGl0bGUgPSAiRnJlbmNoIikgKwogIHRoZW1lX3BhcGVyCmBgYAoKQXJlIHRoZXNlIGNoYW5nZXMgcmVhbGx5IGltcG9ydGFudD8gV2UgbWF5IGV4cGVjdCBhIGNlcnRhaW4gYW1vdW50IG9mIGZsdWN0dWF0aW9uIGJldHdlZW4gYW55IHBhaXIgb2Ygc2Nob29sIHllYXJzLiBXZSBkb24ndCBoYXZlIGRhdGEgZnJvbSBiZWZvcmUgdGhlIDE4LzE5IHNjaG9vbCB5ZWFyLCBidXQgd2UgY2FuIGxvb2sgYXQgaG93IHRoZSBtYWduaXR1ZGUgb2YgY2hhbmdlcyBkdXJpbmcgdGhlIGxvY2tkb3duIHBlcmlvZCBjb21wYXJlcyB0byBjaGFuZ2VzIGVhcmxpZXIgaW4gdGhlIHNjaG9vbCB5ZWFyLgoKVG8ga2VlcCB0aGluZ3MgYXMgY29tcGFyYWJsZSBhcyBwb3NzaWJsZSwgdXNlIGEgc2xpZGluZyB0aW1lIHdpbmRvdyB3aXRoIHRoZSBzYW1lIHNpemUgYXMgdGhlIGxvY2tkb3duIHBlcmlvZDoKYGBge3J9CnRpbWVfd2luZG93IDwtIGFzLm51bWVyaWMocm91bmQoZGF0ZV9zY2hvb2xzX29wZW5lZCAtIGRhdGVfc2Nob29sc19jbG9zZWQpKQp0aW1lX3dpbmRvdwpgYGAKCmBgYHtyfQpkYXRlX3JhbmdlIDwtIHNvcnQodW5pcXVlKHByb2dyZXNzX2J5X2RheSRkb3lfcG9zaXhfYWxpZ25lZCkpCmRhdGVfcmFuZ2UgPC0gZGF0ZV9yYW5nZVtkYXRlX3JhbmdlIDwgZGF0ZV9zY2hvb2xzX2Nsb3NlZF0KCnByb3BfY2hhbmdlX3dpbmRvdyA8LSBkYXRhLnRhYmxlKCkKCmZvciAoaSBpbiAxOihsZW5ndGgoZGF0ZV9yYW5nZSkgLSBhcy5udW1lcmljKHRpbWVfd2luZG93KSkpIHsKICBkIDwtIGRhdGVfcmFuZ2VbaTooaSArIHRpbWVfd2luZG93IC0gMSldCiAgcHJvZ3Jlc3Nfd2luZG93IDwtIHByb2dyZXNzX2J5X2RheVtjb3Vyc2UgJWluJSBjKCJGcmVuY2giLCAiRW5nbGlzaCIpICYgZG95X3Bvc2l4X2FsaWduZWQgJWluJSBkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLih0cmlhbHMgPSBzdW0odHJpYWxzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IC4oc2Nob29sX3llYXIsIGNvdXJzZSwgbGV2ZWwsIHllYXIsIGNoYXB0ZXJfc2ltcGxlKV0KICAKICAjIEZpbGwgaW4gbWlzc2luZyByb3dzIChvY2N1cnMgd2hlbiBjaGFwdGVyIHdhcyBvbmx5IHN0dWRpZWQgaW4gb25lIG9mIHRoZSB0d28geWVhcnMpCiAgcHJvZ3Jlc3Nfd2luZG93IDwtIGFzLmRhdGEudGFibGUodGlkeXI6OmNvbXBsZXRlKHByb2dyZXNzX3dpbmRvdywgdGlkeXI6Om5lc3RpbmcoY291cnNlLCBsZXZlbCwgeWVhciwgY2hhcHRlcl9zaW1wbGUpLCBzY2hvb2xfeWVhciwgZmlsbCA9IGxpc3QodHJpYWxzID0gMCkpKSAKICAKICBwcm9ncmVzc193aW5kb3dbLCBwcm9wIDo9IHRyaWFscy9zdW0odHJpYWxzKSwgYnkgPSAuKHNjaG9vbF95ZWFyLCBjb3Vyc2UsIGxldmVsLCB5ZWFyKV0KCiAgcHJvZ3Jlc3Nfd2luZG93WywgcHJvcF9jaGFuZ2UgOj0gcHJvcFtzY2hvb2xfeWVhciA9PSAiMTkvMjAiXSAtIHByb3Bbc2Nob29sX3llYXIgPT0gIjE4LzE5Il0sIGJ5ID0gLihjb3Vyc2UsIGxldmVsLCB5ZWFyLCBjaGFwdGVyX3NpbXBsZSldCiAgCiAgcHJvcF9jaGFuZ2Vfd2luZG93IDwtIHJiaW5kKHByb3BfY2hhbmdlX3dpbmRvdywgcHJvZ3Jlc3Nfd2luZG93W3NjaG9vbF95ZWFyID09ICIxOS8yMCIsXVssd2luZG93IDo9IGldWyxkYXRlX21pbiA6PSBtaW4oZCldWyxkYXRlX21heCA6PSBtYXgoZCldKQp9CmBgYAoKClRoZSBkZW5zaXR5IG9mIHllYXItdG8teWVhciBjaGFuZ2VzIGNhbiBiZSB2aXN1YWxpc2VkIGJ5IHRpbWUgd2luZG93OgpgYGB7cn0KZ2dwbG90KHByb3BfY2hhbmdlX3dpbmRvdywgYWVzKHggPSBwcm9wX2NoYW5nZSAqIDEwMCwgeSA9IHdpbmRvdywgZ3JvdXAgPSB3aW5kb3cpKSArCiAgZ2dyaWRnZXM6Omdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGEgPSAwLjEsIHNjYWxlID0gMjUsIGZpbGwgPSBOQSkgKwogIGxhYnMoeCA9ICJDaGFuZ2UgaW4gdHJpYWwgc2hhcmVcbihwZXJjZW50YWdlIHBvaW50cykiLAogICAgICAgICB5ID0gIlRpbWUgd2luZG93IikgKwogIHRoZW1lX3BhcGVyCmBgYAoKQ29tcGFyZSB0aGUgYWdncmVnYXRlZCBkZW5zaXR5IHRvIHRoZSBjaGFuZ2VzIGR1cmluZyB0aGUgbG9ja2Rvd24gcGVyaW9kOgpgYGB7cn0KcHJvcF9jaGFuZ2VfY29tYmluZWQgPC0gcmJpbmQocHJvcF9jaGFuZ2Vfd2luZG93WywgcGVyaW9kIDo9ICJQcmUtbG9ja2Rvd24iXSwgcHJvZ3Jlc3NfbG9ja2Rvd25bY291cnNlICVpbiUgYygiRnJlbmNoIiwgIkVuZ2xpc2giKSAmIHNjaG9vbF95ZWFyID09ICIxOS8yMCIsIHBlcmlvZCA6PSAiTG9ja2Rvd24iXSwgZmlsbCA9IFRSVUUpCnByb3BfY2hhbmdlX2NvbWJpbmVkWywgcGVyaW9kIDo9IGZhY3RvcihwZXJpb2QsIGxldmVscyA9IGMoIlByZS1sb2NrZG93biIsICJMb2NrZG93biIpKV0KCmdncGxvdChwcm9wX2NoYW5nZV9jb21iaW5lZCwgYWVzKHggPSBwcm9wX2NoYW5nZSwgY29sb3VyID0gcGVyaW9kKSkgKwogIGdlb21fZGVuc2l0eSgpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGVuZCA9IC41LCBkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSAiQ2hhbmdlIGluIHRyaWFsIHNoYXJlXG4ocGVyY2VudGFnZSBwb2ludHMpIiwKICAgICAgIHkgPSAiRGVuc2l0eSIsCiAgICAgICBjb2xvdXIgPSBOVUxMKSArCiAgdGhlbWVfcGFwZXIKYGBgCgpgYGB7cn0KcHJvcF9jaGFuZ2Vfc2QgPC0gcHJvcF9jaGFuZ2Vfd2luZG93WywgLihzZCA9IHNkKHByb3BfY2hhbmdlKSAqIDEwMCksIGJ5ID0gLihjb3Vyc2UsIHllYXIsIGxldmVsKV0KYGBgCgoKQWRkIGJvdW5kYXJpZXMgYmFzZWQgb24gdGhlIHR5cGljYWwgc3ByZWFkIHRvIHRoZSBjaGFuZ2UgcGxvdDoKYGBge3J9CnBfY2hhbmdlX2ZyZW5jaCA8LSBnZ3Bsb3QocHJvZ3Jlc3NfbG9ja2Rvd25bc2Nob29sX3llYXIgPT0gIjE5LzIwIiAmIGNvdXJzZSA9PSAiRnJlbmNoIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKHllYXIgfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRnJlbmNoIl0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkZyZW5jaCJdLCBhZXMoeW1pbiA9IC1zZCwgeW1heCA9IHNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAsIGZpbGwgPSAiZ3JleTc1IiwgY29sb3VyID0gTkEpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsdHkgPSAyKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gY2hhcHRlcl9zaW1wbGUsIHhlbmQgPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCkpLCB5ZW5kID0gMCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCkpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTIwLCAwLCAyMCkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9ICJDaGFwdGVyIiwKICAgICAgIHkgPSAiQ2hhbmdlIGluIHRyaWFsIHNoYXJlXG4ocGVyY2VudGFnZSBwb2ludHMpIikgKwogIHRoZW1lX3BhcGVyICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpCgpwX2NoYW5nZV9mcmVuY2gKCmdnc2F2ZSgiLi4vb3V0cHV0L3Byb2dyZXNzX2NoYW5nZV9mcmVuY2gucGRmIiwgd2lkdGggPSA1LCBoZWlnaHQgPSA0KQpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jaGFuZ2VfZnJlbmNoLmVwcyIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gNCkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY2hhbmdlX2ZyZW5jaC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDMpCmBgYAoKTWFrZSBhIGNvbWJpbmF0aW9uIHBsb3QgZm9yIGluIHRoZSBwYXBlcjoKYGBge3J9CnBsb3RfZ3JpZChwX2ZyZW5jaF95MSwgcF9mcmVuY2hfeTIsIHBfZnJlbmNoX3kzLCBwX2NoYW5nZV9mcmVuY2gsCiAgICAgICAgICBuY29sID0gMSwKICAgICAgICAgIGFsaWduID0gImh2IiwgYXhpcyA9ICJ0YmxyIiwKICAgICAgICAgIGxhYmVscyA9IGMoIlllYXIgMSIsICJZZWFyIDIiLCAiWWVhciAzLzQiLCAiQ2hhbmdlIiksCiAgICAgICAgICByZWxfaGVpZ2h0cyA9IGMoMSwgMSwgMSwgMS41KSwKICAgICAgICAgIGhqdXN0ID0gLTAuMSwKICAgICAgICAgIHNjYWxlID0gLjk1KQoKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY29tYmlfZnJlbmNoLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY29tYmlfZnJlbmNoLmVwcyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY29tYmlfZnJlbmNoLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKYGBgCgoKYGBge3J9CnBfY2hhbmdlX2ZyZW5jaF95MSA8LSBnZ3Bsb3QocHJvZ3Jlc3NfbG9ja2Rvd25bc2Nob29sX3llYXIgPT0gIjE5LzIwIiAmIGNvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMSJdLCBhZXMoY29sb3VyID0gY2hhcHRlcl9zaW1wbGUpKSArCiAgZmFjZXRfZ3JpZCguIH4gbGV2ZWwsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkZyZW5jaCIgJiB5ZWFyID09ICJZZWFyIDEiXSwgYWVzKHltaW4gPSAtMipzZCwgeW1heCA9IDIqc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMDAsIGZpbGwgPSAiZ3JleTkwIiwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMSJdLCBhZXMoeW1pbiA9IC1zZCwgeW1heCA9IHNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAsIGZpbGwgPSAiZ3JleTc1IiwgY29sb3VyID0gTkEpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsdHkgPSAyKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gY2hhcHRlcl9zaW1wbGUsIHhlbmQgPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCkpLCB5ZW5kID0gMCwgYWxwaGEgPSAuNzUpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgYWxwaGEgPSAuNzUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gYygtMjAsIDAsIDIwKSwgbGltaXRzID0gYygtMzAsIDMwKSwgbGFiZWxzID0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KHN1ZmZpeCA9ICIgcHAiKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQ2hhbmdlIikgKwogIHRoZW1lX3BhcGVyICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpKQoKcF9jaGFuZ2VfZnJlbmNoX3kyIDwtIGdncGxvdChwcm9ncmVzc19sb2NrZG93bltzY2hvb2xfeWVhciA9PSAiMTkvMjAiICYgY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAyIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMiJdLCBhZXMoeW1pbiA9IC0yKnNkLCB5bWF4ID0gMipzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwMCwgZmlsbCA9ICJncmV5OTAiLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAyIl0sIGFlcyh5bWluID0gLXNkLCB5bWF4ID0gc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMCwgZmlsbCA9ICJncmV5NzUiLCBjb2xvdXIgPSBOQSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeGVuZCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIHllbmQgPSAwLCBhbHBoYSA9IC43NSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCkpLCBhbHBoYSA9IC43NSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKC0yMCwgMCwgMjApLCAgbGltaXRzID0gYygtMzAsIDMwKSwgbGFiZWxzID0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KHN1ZmZpeCA9ICIgcHAiKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQ2hhbmdlIikgKwogIHRoZW1lX3BhcGVyICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpKQoKcF9jaGFuZ2VfZnJlbmNoX3kzIDwtIGdncGxvdChwcm9ncmVzc19sb2NrZG93bltzY2hvb2xfeWVhciA9PSAiMTkvMjAiICYgY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAzLzQiXSwgYWVzKGNvbG91ciA9IGNoYXB0ZXJfc2ltcGxlKSkgKwogIGZhY2V0X2dyaWQoLiB+IGxldmVsLCBzY2FsZXMgPSAiZnJlZV94IikgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJGcmVuY2giICYgeWVhciA9PSAiWWVhciAzLzQiXSwgYWVzKHltaW4gPSAtMipzZCwgeW1heCA9IDIqc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMDAsIGZpbGwgPSAiZ3JleTkwIiwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoZGF0YSA9IHByb3BfY2hhbmdlX3NkW2NvdXJzZSA9PSAiRnJlbmNoIiAmIHllYXIgPT0gIlllYXIgMy80Il0sIGFlcyh5bWluID0gLXNkLCB5bWF4ID0gc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMCwgZmlsbCA9ICJncmV5NzUiLCBjb2xvdXIgPSBOQSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeGVuZCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIHllbmQgPSAwLCBhbHBoYSA9IC43NSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeSA9IChwcm9wX2NoYW5nZSAqIDEwMCkpLCBhbHBoYSA9IC43NSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBjKC0yMCwgMCwgMjApLCAgbGltaXRzID0gYygtMzAsIDMwKSwgbGFiZWxzID0gc2NhbGVzOjpudW1iZXJfZm9ybWF0KHN1ZmZpeCA9ICIgcHAiKSkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGNvbG91ciA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiQ2hhbmdlIikgKwogIHRoZW1lX3BhcGVyICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInRyYW5zcGFyZW50IiwgY29sb3VyID0gTkEpKQoKZmlsbGVyX3Bsb3QgPC0gcXBsb3QoKSArIAogIHRoZW1lX25vdGhpbmcoKSArIAogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCnBsb3RfZ3JpZCgKICAgICAgICAgIGZpbGxlcl9wbG90LAogICAgICAgICAgcF9mcmVuY2hfeTEsIGZpbGxlcl9wbG90LCBwX2NoYW5nZV9mcmVuY2hfeTEsIGZpbGxlcl9wbG90LAogICAgICAgICAgcF9mcmVuY2hfeTIsIGZpbGxlcl9wbG90LCBwX2NoYW5nZV9mcmVuY2hfeTIsIGZpbGxlcl9wbG90LCAKICAgICAgICAgIHBfZnJlbmNoX3kzLCBmaWxsZXJfcGxvdCwgcF9jaGFuZ2VfZnJlbmNoX3kzLAogICAgICAgICAgbmNvbCA9IDEsCiAgICAgICAgICBhbGlnbiA9ICJodiIsIGF4aXMgPSAidGJsciIsCiAgICAgICAgICBsYWJlbHMgPSBjKE5BLAogICAgICAgICAgICAgICAgICAgICAiWWVhciAxIiwgTkEsIE5BLCBOQSwKICAgICAgICAgICAgICAgICAgICAgIlllYXIgMiIsIE5BLCBOQSwgTkEsCiAgICAgICAgICAgICAgICAgICAgICJZZWFyIDMvNCIsIE5BLCBOQSksCiAgICAgICAgICByZWxfaGVpZ2h0cyA9IGMoLjEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIDEsIC0uMiwgLjc1LCAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAxLCAtLjIsIC43NSwgLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgMSwgLS4yLCAuNzUpLAogICAgICAgICAgaGp1c3QgPSAtMC4xLAogICAgICAgICAgdmp1c3QgPSAtMC4xLAogICAgICAgICAgc2NhbGUgPSAuOTUpCgpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jb21iaV9hbHRfZnJlbmNoLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY29tYmlfYWx0X2ZyZW5jaC5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDkpCmdnc2F2ZSgiLi4vb3V0cHV0L3Byb2dyZXNzX2NvbWJpX2FsdF9mcmVuY2gucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA5KQpgYGAKCgpNYWtlIGEgdGFibGUgb2YgdGhlIGNoYW5nZXM6CmBgYHtyfQpwcm9ncmVzc190YWJsZSA8LSBwcm9ncmVzc19sb2NrZG93bltzY2hvb2xfeWVhciA9PSAiMTkvMjAiLCAuKGNvdXJzZSwgeWVhciwgbGV2ZWwsIGNoYXB0ZXJfc2ltcGxlLCBwcm9wX2NoYW5nZSldW3Byb3BfY2hhbmdlX3NkLCBvbiA9IC4oY291cnNlLCB5ZWFyLCBsZXZlbCldCgpwcm9ncmVzc190YWJsZSA8LSBwcm9ncmVzc190YWJsZVtsZXZlbCAhPSAiT3RoZXIiXVssIC4oQ291cnNlID0gY291cnNlLCBZZWFyID0gc3ViKHllYXIsIHBhdHRlcm4gPSAiWWVhciAiLCByZXBsYWNlbWVudCA9ICIiKSwgTGV2ZWwgPSBpZmVsc2UoZ3JlcGwoInZtYm8iLCBsZXZlbCksICJ2bWJvIiwgaWZlbHNlKGdyZXBsKCJoYXZvIiwgbGV2ZWwpLCAiaGF2byIsICJ2d28iKSksIGBCYXNlbGluZSAoMSBTRClgID0gc2NhbGVzOjpwZXJjZW50KHNkLzEwMCwgYWNjdXJhY3kgPSAuMDEsIHN1ZmZpeCA9ICIiKSwgQ2hhcHRlciA9IGNoYXB0ZXJfc2ltcGxlLCBDaGFuZ2UgPSBzY2FsZXM6OnBlcmNlbnQocHJvcF9jaGFuZ2UsIGFjY3VyYWN5ID0gLjAxLCBwcmVmaXggPSBpZmVsc2UocHJvcF9jaGFuZ2UgPiAwLCAiKyIsICIiKSwgc3VmZml4ID0gIiIpKV0KCnByb2dyZXNzX3RhYmxlIDwtIHRpZHlyOjpwaXZvdF93aWRlcihwcm9ncmVzc190YWJsZSwgQ291cnNlOmBCYXNlbGluZSAoMSBTRClgLCBuYW1lc19mcm9tID0gQ2hhcHRlciwgbmFtZXNfcHJlZml4ID0gIkNoLiAiLCB2YWx1ZXNfZnJvbSA9IENoYW5nZSkKCnNldG9yZGVyKHByb2dyZXNzX3RhYmxlLCAtQ291cnNlLCBZZWFyKQpwcm9ncmVzc190YWJsZQoKb3B0aW9ucyhrbml0ci5rYWJsZS5OQSA9ICIiKQpwcm9ncmVzc190YWJsZV90ZXggPC0ga25pdHI6OmthYmxlKHByb2dyZXNzX3RhYmxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduID0gYygibCIsICJsIiwgImwiLCByZXAoInIiLCAxMikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1hdCA9ICJsYXRleCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9va3RhYnMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVzY2FwZSA9IEZBTFNFKQoKcHJvZ3Jlc3NfdGFibGVfdGV4IDwtIGthYmxlRXh0cmE6OmNvbGxhcHNlX3Jvd3MocHJvZ3Jlc3NfdGFibGVfdGV4LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1ucyA9IDE6MiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlnbiA9ICJ0b3AiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXRleF9obGluZSA9ICJtYWpvciIpCgpjYXQocHJvZ3Jlc3NfdGFibGVfdGV4LCBmaWxlID0gIi4uL291dHB1dC9wcm9ncmVzc190YWJsZS50ZXgiKQpgYGAKCgoKCiMjIyBFbmdsaXNoCgpOT1RFOiBjaGFwdGVycyB3aXRob3V0IGEgbnVtYmVyIChCcmlkZ2luZyB0aGUgR2FwLCBFeGFtIFByZXBhcmF0aW9uKSBhcmUgc2hvd24gYXMgIk8iIGluIHRoZSBwbG90LgpUaGV5IGRvbid0IHNlZW0gdG8gZml0IG5lYXRseSBpbiB0aGUgY2hhcHRlciBzZXF1ZW5jZSwgc28gSSdtIGdyb3VwaW5nIHRoZW0gdG9nZXRoZXIuCmBgYHtyfQpwX2VuZ2xpc2hfeTEgPC0gZ2dwbG90KHByb2dyZXNzX2J5X3dlZWtbbGV2ZWwgIT0gIk90aGVyIl1bLCBsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXVtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgeWVhciA9PSAiWWVhciAxIl0sIGFlcyh4ID0gYXMuUE9TSVhjdChkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKSwgeSA9IHByb3AsIGZpbGwgPSBjaGFwdGVyX3NpbXBsZSwgZ3JvdXAgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKHNjaG9vbF95ZWFyIH4gbGV2ZWwsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fY29sKGFscGhhID0gMC43NSwgd2lkdGggPSA3KjI0KjYwKjYwLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMC4wMSwgeW1heCA9IDEuMDEsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgbHR5ID0gMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IGMoMCwgLjUgLCAxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAyKSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfZW5nbGlzaF95MiA8LSBnZ3Bsb3QocHJvZ3Jlc3NfYnlfd2Vla1tsZXZlbCAhPSAiT3RoZXIiXVssIGxldmVsIDo9IGZhY3RvcihsZXZlbCldW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiB5ZWFyID09ICJZZWFyIDIiXSwgYWVzKHggPSBhcy5QT1NJWGN0KGRveV9wb3NpeF9hbGlnbmVkX3dlZWspLCB5ID0gcHJvcCwgZmlsbCA9IGNoYXB0ZXJfc2ltcGxlLCBncm91cCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQoc2Nob29sX3llYXIgfiBsZXZlbCwgZHJvcCA9IEZBTFNFKSArCiAgZ2VvbV9jb2woYWxwaGEgPSAwLjc1LCB3aWR0aCA9IDcqMjQqNjAqNjAsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KHhtaW4gPSBkYXRlX3NjaG9vbHNfY2xvc2VkLCB4bWF4ID0gZGF0ZV9zY2hvb2xzX29wZW5lZCwgeW1pbiA9IC0wLjAxLCB5bWF4ID0gMS4wMSwgZmlsbCA9IE5BLCBjb2xvdXIgPSAiYmxhY2siLCBsdHkgPSAyKSArCiAgc2NhbGVfeF9kYXRldGltZShleHBhbmQgPSBjKDAsIDApLCAKICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGFzLlBPU0lYY3QoYygKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTAtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMTktMTItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDItMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDQtMDEgMDI6MDA6MDAgQ0VUIiwKICAgICAgICAgICAgICAgICAgICAgIjIwMjAtMDYtMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgbGltaXRzID0gYXMuUE9TSVhjdChjKCIyMDE5LTA5LTAxIDAyOjAwOjAwIENFVCIsICIyMDIwLTA3LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGRhdGVfbGFiZWxzID0gIiViIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCksIGxpbWl0cyA9IGMoMCwxKSwgYnJlYWtzID0gYygwLCAuNSAsIDEpKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEsIG5hLnRyYW5zbGF0ZSA9IEZBTFNFKSArCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobmNvbCA9IDIpKSArCiAgbGFicyh4ID0gTlVMTCwKICAgICAgIHkgPSAiU2hhcmUgb2YgdHJpYWxzIiwKICAgICAgIGZpbGwgPSAiQ2hhcHRlciIpICsKICB0aGVtZV9wYXBlcgoKcF9lbmdsaXNoX3kzIDwtIGdncGxvdChwcm9ncmVzc19ieV93ZWVrW2xldmVsICE9ICJPdGhlciJdWywgbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV1bY291cnNlID09ICJFbmdsaXNoIiAmIHllYXIgPT0gIlllYXIgMyJdLCBhZXMoeCA9IGFzLlBPU0lYY3QoZG95X3Bvc2l4X2FsaWduZWRfd2VlayksIHkgPSBwcm9wLCBmaWxsID0gY2hhcHRlcl9zaW1wbGUsIGdyb3VwID0gc2Nob29sX3llYXIpKSArCiAgZmFjZXRfZ3JpZChzY2hvb2xfeWVhciB+IGxldmVsLCBkcm9wID0gRkFMU0UpICsKICBnZW9tX2NvbChhbHBoYSA9IDAuNzUsIHdpZHRoID0gNyoyNCo2MCo2MCwgY29sb3VyID0gTkEpICsKICBnZW9tX3JlY3QoeG1pbiA9IGRhdGVfc2Nob29sc19jbG9zZWQsIHhtYXggPSBkYXRlX3NjaG9vbHNfb3BlbmVkLCB5bWluID0gLTAuMDEsIHltYXggPSAxLjAxLCBmaWxsID0gTkEsIGNvbG91ciA9ICJibGFjayIsIGx0eSA9IDIpICsKICBzY2FsZV94X2RhdGV0aW1lKGV4cGFuZCA9IGMoMCwgMCksIAogICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYXMuUE9TSVhjdChjKAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAxOS0xMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wMi0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNC0wMSAwMjowMDowMCBDRVQiLAogICAgICAgICAgICAgICAgICAgICAiMjAyMC0wNi0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBhcy5QT1NJWGN0KGMoIjIwMTktMDktMDEgMDI6MDA6MDAgQ0VUIiwgIjIwMjAtMDctMDEgMDI6MDA6MDAgQ0VUIikpLAogICAgICAgICAgICAgICAgICAgZGF0ZV9sYWJlbHMgPSAiJWIiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSwgbGltaXRzID0gYygwLDEpLCBicmVha3MgPSBjKDAsIC41ICwgMSkpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChuY29sID0gMikpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJTaGFyZSBvZiB0cmlhbHMiLAogICAgICAgZmlsbCA9ICJDaGFwdGVyIikgKwogIHRoZW1lX3BhcGVyCgpwX2VuZ2xpc2hfeTQgPC0gZ2dwbG90KHByb2dyZXNzX2J5X3dlZWtbbGV2ZWwgIT0gIk90aGVyIl1bLCBsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXVtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgeWVhciA9PSAiWWVhciA0Il0sIGFlcyh4ID0gYXMuUE9TSVhjdChkb3lfcG9zaXhfYWxpZ25lZF93ZWVrKSwgeSA9IHByb3AsIGZpbGwgPSBjaGFwdGVyX3NpbXBsZSwgZ3JvdXAgPSBzY2hvb2xfeWVhcikpICsKICBmYWNldF9ncmlkKHNjaG9vbF95ZWFyIH4gbGV2ZWwsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fY29sKGFscGhhID0gMC43NSwgd2lkdGggPSA3KjI0KjYwKjYwLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fcmVjdCh4bWluID0gZGF0ZV9zY2hvb2xzX2Nsb3NlZCwgeG1heCA9IGRhdGVfc2Nob29sc19vcGVuZWQsIHltaW4gPSAtMC4wMSwgeW1heCA9IDEuMDEsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgbHR5ID0gMikgKwogIHNjYWxlX3hfZGF0ZXRpbWUoZXhwYW5kID0gYygwLCAwKSwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBhcy5QT1NJWGN0KGMoCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEwLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDE5LTEyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTAyLTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA0LTAxIDAyOjAwOjAwIENFVCIsCiAgICAgICAgICAgICAgICAgICAgICIyMDIwLTA2LTAxIDAyOjAwOjAwIENFVCIpKSwKICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLlBPU0lYY3QoYygiMjAxOS0wOS0wMSAwMjowMDowMCBDRVQiLCAiMjAyMC0wNy0wMSAwMjowMDowMCBDRVQiKSksCiAgICAgICAgICAgICAgICAgICBkYXRlX2xhYmVscyA9ICIlYiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IGMoMCwgLjUgLCAxKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAyKSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIlNoYXJlIG9mIHRyaWFscyIsCiAgICAgICBmaWxsID0gIkNoYXB0ZXIiKSArCiAgdGhlbWVfcGFwZXIKCnBfcHJvZ3Jlc3NfZW5nbGlzaCA8LSBwbG90X2dyaWQocF9lbmdsaXNoX3kxLCBwX2VuZ2xpc2hfeTIsIHBfZW5nbGlzaF95MywgcF9lbmdsaXNoX3k0LAogICAgICAgICAgbmNvbCA9IDEsCiAgICAgICAgICBhbGlnbiA9ICJodiIsIGF4aXMgPSAidGJsciIsCiAgICAgICAgICBsYWJlbHMgPSBjKCJZZWFyIDEiLCAiWWVhciAyIiwgIlllYXIgMyIsICJZZWFyIDQiKSwKICAgICAgICAgIGhqdXN0ID0gLTAuMSwKICAgICAgICAgIHNjYWxlID0gLjk1KQoKcF9wcm9ncmVzc19lbmdsaXNoCgpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19lbmdsaXNoLnBkZiIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfZW5nbGlzaC5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDkpCmdnc2F2ZSgiLi4vb3V0cHV0L3Byb2dyZXNzX2VuZ2xpc2gucG5nIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA5KQpgYGAKCkRpZCB0aGUgc2hhcmUgb2YgdHJpYWxzIGNoYW5nZSBiZXR3ZWVuIHNjaG9vbCB5ZWFycz8KCmBgYHtyfQpnZ3Bsb3QocHJvZ3Jlc3NfbG9ja2Rvd25bY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciJdLCBhZXMoeCA9IHNjaG9vbF95ZWFyLCB5ID0gcHJvcCwgZmlsbCA9IGNoYXB0ZXJfc2ltcGxlLCBncm91cCA9IHNjaG9vbF95ZWFyKSkgKwogIGZhY2V0X2dyaWQobGV2ZWwgfiB5ZWFyKSArCiAgZ2VvbV9jb2woY29sb3VyID0gTkEpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpLCBsaW1pdHMgPSBjKDAsMSkpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChkaXJlY3Rpb24gPSAtMSwgbmEudHJhbnNsYXRlID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLAogICAgICAgeSA9ICJTaGFyZSBvZiB0cmlhbHMiLAogICAgICAgZmlsbCA9ICJDaGFwdGVyIiwKICAgICAgIHRpdGxlID0gIkVuZ2xpc2giKSArCiAgdGhlbWVfcGFwZXIKYGBgCgpDaGFuZ2UgYmV0d2VlbiBzY2hvb2wgeWVhcnM6CmBgYHtyfQpwX2NoYW5nZV9lbmdsaXNoIDwtIGdncGxvdChwcm9ncmVzc19sb2NrZG93bltzY2hvb2xfeWVhciA9PSAiMTkvMjAiICYgY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciJdLCBhZXMoY29sb3VyID0gY2hhcHRlcl9zaW1wbGUpKSArCiAgZmFjZXRfZ3JpZCh5ZWFyIH4gbGV2ZWwsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIl0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIl0sIGFlcyh5bWluID0gLXNkLCB5bWF4ID0gc2QpLCB4bWluID0gMCwgeG1heCA9IDEwMCwgZmlsbCA9ICJncmV5NzUiLCBjb2xvdXIgPSBOQSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSBjaGFwdGVyX3NpbXBsZSwgeGVuZCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIHllbmQgPSAwKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9ICJDaGFwdGVyIiwKICAgICAgIHkgPSAiQ2hhbmdlIGluIHRyaWFsIHNoYXJlXG4ocGVyY2VudGFnZSBwb2ludHMpIikgKwogIHRoZW1lX3BhcGVyICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpCgpwX2NoYW5nZV9lbmdsaXNoIAoKZ2dzYXZlKCIuLi9vdXRwdXQvcHJvZ3Jlc3NfY2hhbmdlX2VuZ2xpc2gucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA2KQpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jaGFuZ2VfZW5nbGlzaC5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDYpCmdnc2F2ZSgiLi4vb3V0cHV0L3Byb2dyZXNzX2NoYW5nZV9lbmdsaXNoLnBuZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNikKYGBgCgpQZXJmb3JtIGEgY2hpLXNxdWFyZSB0ZXN0IG9mIGhvbW9nZW5laXR5IHRvIGRldGVybWluZSB3aGV0aGVyIHNjaG9vbCB5ZWFycyBhcmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQuCgpgYGB7cn0KZm9yICh5IGluIHNvcnQodW5pcXVlKHByb2dyZXNzX2xvY2tkb3duJHllYXIpKSkgewogIGZvciAobCBpbiBsZXZlbHMocHJvZ3Jlc3NfbG9ja2Rvd24kbGV2ZWwpKSB7CiAgICBkIDwtIHByb2dyZXNzX2xvY2tkb3duW2NvdXJzZSA9PSAiRW5nbGlzaCIgJiB5ZWFyID09IHkgJiBsZXZlbCA9PSBsXQogICAgaWYgKG5yb3coZCkgPiAwKSB7CiAgICAgIHByaW50KHBhc3RlKCJFbmdsaXNoIiwgeSwgbCwgY29sbGFwc2U9ICIgIikpCiAgICAgIHByaW50KAogICAgICAgIGNoaXNxLnRlc3QoCiAgICAgICAgICBkY2FzdChkLCBzY2hvb2xfeWVhciB+IGNoYXB0ZXJfc2ltcGxlLCB2YWx1ZS52YXIgPSAidHJpYWxzIiwgZmlsbCA9IDApWywgc2Nob29sX3llYXIgOj0gTlVMTF0KICAgICAgICApCiAgICAgICkKICAgIH0KICB9IAp9CmBgYAoKQ29uY2x1c2lvbjogYWxsIHRlc3RzIGluZGljYXRlIGEgZGlmZmVyZW5jZSBpbiBwcm9wb3J0aW9ucyBiZXR3ZWVuIHNjaG9vbCB5ZWFycyAocCA8PCAwLjAwMSkuCgoKTWFrZSBhIGNvbWJpbmF0aW9uIHBsb3QgZm9yIGluIHRoZSBwYXBlcjoKYGBge3J9CnByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2ggPC0gcHJvZ3Jlc3NfbG9ja2Rvd25bbGV2ZWwgIT0gIk90aGVyIiAmIHNjaG9vbF95ZWFyID09ICIxOS8yMCIgJiBjb3Vyc2UgPT0gIkVuZ2xpc2giXQpwcm9ncmVzc19sb2NrZG93bl9lbmdsaXNoWywgbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0KCnBfY2hhbmdlX2VuZ2xpc2hfeTEgPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2hbeWVhciA9PSAiWWVhciAxIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiB5ZWFyID09ICJZZWFyIDEiXVssbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHllYXIgPT0gIlllYXIgMSJdWyxsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDAsIGFscGhhID0gLjc1KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIGFscGhhID0gLjc1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTEwLCAwLCAxMCksIGxpbWl0cyA9IGMoLTIwLCAyMCksIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyX2Zvcm1hdChzdWZmaXggPSAiIHBwIikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkNoYW5nZSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCnBfY2hhbmdlX2VuZ2xpc2hfeTIgPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2hbeWVhciA9PSAiWWVhciAyIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiB5ZWFyID09ICJZZWFyIDIiXVssbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHllYXIgPT0gIlllYXIgMiJdWyxsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDAsIGFscGhhID0gLjc1KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIGFscGhhID0gLjc1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTEwLCAwLCAxMCksIGxpbWl0cyA9IGMoLTIwLCAyMCksIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyX2Zvcm1hdChzdWZmaXggPSAiIHBwIikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkNoYW5nZSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCnBfY2hhbmdlX2VuZ2xpc2hfeTMgPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2hbeWVhciA9PSAiWWVhciAzIl0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiB5ZWFyID09ICJZZWFyIDMiXVssbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHllYXIgPT0gIlllYXIgMyJdWyxsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDAsIGFscGhhID0gLjc1KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIGFscGhhID0gLjc1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTEwLCAwLCAxMCksIGxpbWl0cyA9IGMoLTIwLCAyMCksIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyX2Zvcm1hdChzdWZmaXggPSAiIHBwIikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkNoYW5nZSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCnBfY2hhbmdlX2VuZ2xpc2hfeTQgPC0gZ2dwbG90KHByb2dyZXNzX2xvY2tkb3duX2VuZ2xpc2hbeWVhciA9PSAiWWVhciA0Il0sIGFlcyhjb2xvdXIgPSBjaGFwdGVyX3NpbXBsZSkpICsKICBmYWNldF9ncmlkKC4gfiBsZXZlbCwgc2NhbGVzID0gImZyZWVfeCIsIGRyb3AgPSBGQUxTRSkgKwogIGdlb21fcmVjdChkYXRhID0gcHJvcF9jaGFuZ2Vfc2RbY291cnNlID09ICJFbmdsaXNoIiAmIGxldmVsICE9ICJPdGhlciIgJiB5ZWFyID09ICJZZWFyIDQiXVssbGV2ZWwgOj0gZmFjdG9yKGxldmVsKV0sIGFlcyh5bWluID0gLTIqc2QsIHltYXggPSAyKnNkKSwgeG1pbiA9IDAsIHhtYXggPSAxMDAwLCBmaWxsID0gImdyZXk5MCIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBwcm9wX2NoYW5nZV9zZFtjb3Vyc2UgPT0gIkVuZ2xpc2giICYgbGV2ZWwgIT0gIk90aGVyIiAmIHllYXIgPT0gIlllYXIgNCJdWyxsZXZlbCA6PSBmYWN0b3IobGV2ZWwpXSwgYWVzKHltaW4gPSAtc2QsIHltYXggPSBzZCksIHhtaW4gPSAwLCB4bWF4ID0gMTAwLCBmaWxsID0gImdyZXk3NSIsIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB4ZW5kID0gY2hhcHRlcl9zaW1wbGUsIHkgPSAocHJvcF9jaGFuZ2UgKiAxMDApKSwgeWVuZCA9IDAsIGFscGhhID0gLjc1KSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGNoYXB0ZXJfc2ltcGxlLCB5ID0gKHByb3BfY2hhbmdlICogMTAwKSksIGFscGhhID0gLjc1KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoLTEwLCAwLCAxMCksIGxpbWl0cyA9IGMoLTIwLCAyMCksIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyX2Zvcm1hdChzdWZmaXggPSAiIHBwIikpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKGRpcmVjdGlvbiA9IC0xLCBuYS50cmFuc2xhdGUgPSBGQUxTRSkgKwogIGd1aWRlcyhjb2xvdXIgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsCiAgICAgICB5ID0gIkNoYW5nZSIpICsKICB0aGVtZV9wYXBlciArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSwKICAgICAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGNvbG91ciA9IE5BKSkKCmZpbGxlcl9wbG90IDwtIHFwbG90KCkgKyAKICB0aGVtZV9ub3RoaW5nKCkgKyAKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSksCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAidHJhbnNwYXJlbnQiLCBjb2xvdXIgPSBOQSkpCgpwbG90X2dyaWQoCiAgICAgICAgICBmaWxsZXJfcGxvdCwKICAgICAgICAgIHBfZW5nbGlzaF95MSwgZmlsbGVyX3Bsb3QsIHBfY2hhbmdlX2VuZ2xpc2hfeTEsIGZpbGxlcl9wbG90LAogICAgICAgICAgcF9lbmdsaXNoX3kyLCBmaWxsZXJfcGxvdCwgcF9jaGFuZ2VfZW5nbGlzaF95MiwgZmlsbGVyX3Bsb3QsIAogICAgICAgICAgcF9lbmdsaXNoX3kzLCBmaWxsZXJfcGxvdCwgcF9jaGFuZ2VfZW5nbGlzaF95MywgZmlsbGVyX3Bsb3QsCiAgICAgICAgICBwX2VuZ2xpc2hfeTQsIGZpbGxlcl9wbG90LCBwX2NoYW5nZV9lbmdsaXNoX3k0LAogICAgICAgICAgbmNvbCA9IDEsCiAgICAgICAgICBhbGlnbiA9ICJodiIsIGF4aXMgPSAidGJsciIsCiAgICAgICAgICBsYWJlbHMgPSBjKE5BLAogICAgICAgICAgICAgICAgICAgICAiWWVhciAxIiwgTkEsIE5BLCBOQSwKICAgICAgICAgICAgICAgICAgICAgIlllYXIgMiIsIE5BLCBOQSwgTkEsCiAgICAgICAgICAgICAgICAgICAgICJZZWFyIDMiLCBOQSwgTkEsIE5BLAogICAgICAgICAgICAgICAgICAgICAiWWVhciA0IiwgTkEsIE5BKSwKICAgICAgICAgIHJlbF9oZWlnaHRzID0gYyguMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgMSwgLS4yLCAuNzUsIC4xLAogICAgICAgICAgICAgICAgICAgICAgICAgIDEsIC0uMiwgLjc1LCAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAxLCAtLjIsIC43NSwgLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgMSwgLS4yLCAuNzUpLAogICAgICAgICAgaGp1c3QgPSAtMC4xLAogICAgICAgICAgdmp1c3QgPSAtMC4xLAogICAgICAgICAgc2NhbGUgPSAuOTUpCgpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jb21iaV9hbHRfZW5nbGlzaC5wZGYiLCB3aWR0aCA9IDksIGhlaWdodCA9IDExKQpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jb21iaV9hbHRfZW5nbGlzaC5lcHMiLCB3aWR0aCA9IDksIGhlaWdodCA9IDExKQpnZ3NhdmUoIi4uL291dHB1dC9wcm9ncmVzc19jb21iaV9hbHRfZW5nbGlzaC5wbmciLCB3aWR0aCA9IDksIGhlaWdodCA9IDExKQpgYGAKCiMgU2Vzc2lvbiBpbmZvCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK